#!/bin/sh

usage() {
cat<<'EOF'
Makes `kpatch'es for the makesystem in the current directory.

Usage:	libcare-patch-make [-h|--help] [-u|--update || -c|--clean]
	[-s|--srcdir=SRCDIR] \
	[-d|--destdir=DESTDIRVAR] \
	[-i|--id=PATCH-ID] \
	[-b|--buildid=BUILD-ID] \
	[-j|--jobs=<number of jobs>] \
	PATCH1 PATCH2 ...

Run from inside the directory with `make'ble software. Makesystem must support
install with specified DESTDIR.

  -c --clean	do a clean build, execute `make clean` first
  -u --update	only update existing patches without rebuild. useful when
		working on patch utils.
  -d --destdir	specify variable makefile system uses to specify destination
		directory for the installation
  -i --id	give this patch an unique id (less than 7 char)
  -b --buildid	give this patch an unique build-id
  -j --jobs 	specify variable makefile system jobs of compile, for example
  		`make -j 4` is startup `4` jobs to compile.
EOF
		exit ${1-0}
}


prepare_env() {
	KPATCH_PATH=$(dirname $0)
	export KP_EXECVE_DEBUG=1;
	export KPCC_DEBUG=1;
	export KPATCH_PATH=$(dirname $0)

	if test ! -x "$KPATCH_PATH/kpatch_gensrc"; then
		echo "kpatch tools are missing" >&2
		exit 1
	fi

	export IS_LIBCARE_CC=y
	export CC=$KPATCH_PATH/libcare-cc
	export CXX=$CC

	MAKE_OUTPUT=/dev/stdout

	LPMAKEFILE=""
	test -f lpmakefile && LPMAKEFILE="-f lpmakefile"

	LPMAKE_ORIGINAL_DIR="${LPMAKE_ORIGINAL_DIR-$PWD/lpmake}"
	LPMAKE_PATCHED_DIR="${LPMAKE_PATCHED_DIR-$PWD/.lpmaketmp/patched}"
	LPMAKE_PATCHROOT="${LPMAKE_PATCHROOT-$PWD/patchroot}"

	LIBCARE_CC=$CC
	SYMBOLINK_CC=$(which cc)
	REAL_CC=$(realpath $SYMBOLINK_CC)

	export LPMAKE_ORIGINAL_DIR LPMAKE_PATCHED_DIR LPMAKE_PATCHROOT
	mkdir -p "$LPMAKE_ORIGINAL_DIR" "$LPMAKE_PATCHED_DIR" "$LPMAKE_PATCHROOT"

	unset MAKELEVEL
	unset MAKEFLAGS

	red=$(tput setaf 1)
	green=$(tput setaf 2)
	reset=$(tput sgr0)
}

restore_origs() {
	find $srcdir -regex '.+\.[0-9]+\.lpmakeorig' | awk '
	{
		origfname = $0;
		gsub("\.[0-9]+\.lpmakeorig$", "");
		fname = $0;
		if (!vers[fname] || vers[fname] > origfname)
			{ vers[fname] = origfname; }
	}
	END { for (f in vers) system("mv " vers[f] " " f); }
' > /dev/null 2>&1
}

trap "restore_origs" 0

replace_qemu_ld_flags() {
	local qemu_ld_flags_old=$1
	ret=$(echo $qemu_ld_flags_old | grep "\-Wl,-q")
	if [[ "$ret" == "" ]]; then
		local qemu_ld_flags="${qemu_ld_flags_old} -Wl,-q"
		echo "replace QEMU_LDFLAGS to '${qemu_ld_flags}'"
		sed -i "/^QEMU_LDFLAGS=/c\\${qemu_ld_flags}" config-host.mak
	fi
}

recover_qemu_ld_flags() {
	local qemu_ld_flags=$1
	echo "recover QEMU_LDFLAGS to '${qemu_ld_flags}'"
	sed -i "/^QEMU_LDFLAGS=/c\\${qemu_ld_flags}" config-host.mak
}

replace_cc_symbolink() {
	unlink $SYMBOLINK_CC
	ln -s $LIBCARE_CC $SYMBOLINK_CC
}

recover_cc_symbolink() {
	unlink $SYMBOLINK_CC
	ln -s $REAL_CC $SYMBOLINK_CC
}

trap "recover_cc_symbolink" SIGINT SIGTERM SIGQUIT

build_objects() {
	restore_origs

	JOBS_MAKE=""
	test $jobs_make && JOBS_MAKE="-j $jobs_make"

	if test -n "$do_clean"; then
		make $LPMAKEFILE $JOBS_MAKE clean >$MAKE_OUTPUT 2>&1
		rm -rf "$LPMAKE_ORIGINAL_DIR" "$LPMAKE_PATCHED_DIR"
	fi

	export KPATCH_STAGE=original
	export KPCC_DBGFILTER_ARGS=""

	echo "${green}BUILDING ORIGINAL CODE${reset}"
	make $LPMAKEFILE $JOBS_MAKE >$MAKE_OUTPUT 2>&1

	echo "${green}INSTALLING ORIGINAL OBJECTS INTO $LPMAKE_ORIGINAL_DIR${reset}"
	make $LPMAKEFILE $JOBS_MAKE install			\
		"$destdir=$LPMAKE_ORIGINAL_DIR"			\
		>$MAKE_OUTPUT 2>&1

	local oldpwd="$(pwd)"
	if test -n "$srcdir"; then
		cd "$srcdir"
	fi

	i=0
	for patch; do
		echo "${red}applying $patch...${reset}"
		patch -b -z .${i}.lpmakeorig -p1 < $patch
	done

	if test -n "$srcdir"; then
		cd "$oldpwd"
	fi

	export KPATCH_STAGE=patched
	export KPCC_APPEND_ARGS="-Wl,-q"

	qemu_ld_flags_bak=$(grep "^QEMU_LDFLAGS=" config-host.mak)
	#add '-Wl,-q' to LD_FLAGS
	replace_qemu_ld_flags "$qemu_ld_flags_bak"

	echo "${green}BUILDING PATCHED CODE${reset}"
	make $LPMAKEFILE $JOBS_MAKE >$MAKE_OUTPUT 2>&1

	echo "${green}INSTALLING PATCHED OBJECTS INTO $LPMAKE_PATCHED_DIR${reset}"
	make $LPMAKEFILE $JOBS_MAKE install			\
		"$destdir=$LPMAKE_PATCHED_DIR"			\
		>$MAKE_OUTPUT 2>&1

	# recover LD_FLAGS
	recover_qemu_ld_flags "$qemu_ld_flags_bak"
}

build_kpatches() {
	mkdir -p "${LPMAKE_PATCHROOT}"

	echo "${green}MAKING PATCHES${reset}"

	for execfile in $(find "$LPMAKE_ORIGINAL_DIR" -perm /0111 -type f); do
		origexec="$execfile"
		filename="${origexec##*$LPMAKE_ORIGINAL_DIR/}"
		patchedexec="$LPMAKE_PATCHED_DIR/$filename"

		buildid=$(eu-readelf -n "$origexec" | sed -n '/Build ID:/ { s/.* //; p }')
		if ! eu-readelf -S "$patchedexec" | grep -q '.kpatch'; then
			continue
		fi

		test -n "$buildid" || continue

		if [ ! -z ${build_id} ];then
		    buildid=${build_id}
		fi

		chmod u+w "${origexec}" "${patchedexec}"
		$KPATCH_PATH/kpatch_strip --strip "${patchedexec}" \
			"${patchedexec}.stripped" >/dev/null
		if [ $? -ne 0 ];then
			echo "Failed to do stripped."
			exit 1
		fi
		$KPATCH_PATH/kpatch_strip --rel-fixup "$origexec" \
			"${patchedexec}.stripped" || continue
		if [ $? -ne 0 ];then
			echo "Failed to do relocation fixup."
			exit 1
		fi
		/usr/bin/strip --strip-unneeded "${patchedexec}.stripped"
		if [ $? -ne 0 ];then
			echo "Failed to do stripped unneeded section."
			exit 1
		fi
		$KPATCH_PATH/kpatch_strip --undo-link "$origexec" "${patchedexec}.stripped"
		$KPATCH_PATH/kpatch_make -b "$buildid" -i "$patch_id" \
			"${patchedexec}.stripped" -o "${patchedexec}.kpatch"
		if [ $? -ne 0 ];then
			echo "Failed to make patch."
			exit 1
		fi
		cp "${patchedexec}.kpatch" "${LPMAKE_PATCHROOT}"/${buildid}.kpatch
		echo "patch for ${origexec} is in ${LPMAKE_PATCHROOT}/${buildid}.kpatch"
	done
}

main() {
	PROG_NAME=$(basename $0)

	TEMP=$(getopt -o s:ucd:i:b:j: --long srcdir:,update,clean,destdir:,id:,buildid:,jobs: -n ${PROG_NAME} -- "$@" || usage 1)
	eval set -- "$TEMP"

	destdir="DESTDIR"
	while true; do
		case $1 in
		-s|--srcdir)
			shift
			srcdir="$1"
			shift
			;;
		-u|--update)
			shift
			only_update=1
			;;
		-c|--clean)
			shift
			do_clean=1
			;;
		-d|--destdir)
			shift
			destdir=$1
			shift
			;;
		-i|--id)
			shift
			patch_id="$1"
			shift
			;;
		-b|--buildid)
			shift
			build_id="$1"
			shift
			;;
		-j|--jobs)
			shift
			jobs_make=$1
			shift
			;;
		--)
			shift; break;
			;;
		*)
			usage 1
			;;
		esac
	done

	if [ -z "$patch_id" ] || [ ${#patch_id} -gt 7 ];then
		echo "Invalid patch-id!" >&2
		usage 1
	fi

	prepare_env

	# replace cc
	replace_cc_symbolink

	if test -z "$only_update"; then
		build_objects "$@"
	fi
	build_kpatches

	# recover cc
	recover_cc_symbolink
}

main "$@"
