#! /bin/bash
#########################################################################
#									#
#	kern-bld is automatically generated,				#
#		please do not modify!					#
#									#
#########################################################################

#########################################################################
#									#
# Author: Copyright (C) 2016-2019, 2021, 2024  Mark Grant		#
#									#
# Released under the GPLv3 only.					#
# SPDX-License-Identifier: GPL-3.0-only					#
#									#
# Purpose:								#
# Builds the specified kernel in the specified manner.			#
#						     		 	#
# Syntax:	See usage().						#
#									#
# Exit codes used:-							#
# Bash standard Exit Codes:	0 - success				#
#				1 - general failure			#
# User-defined exit code range is 64 - 113				#
#	C/C++ Semi-standard exit codes from sysexits.h range is 64 - 78	#
#		EX_USAGE	64	command line usage error	#
#		EX_DATAERR	65	data format error		#
#		EX_NOINPUT	66	cannot open input		#
#		EX_NOUSER	67	addressee unknown		#
#		EX_NOHOST	68	host name unknown		#
#		EX_UNAVAILABLE	69	service unavailable		#
#		EX_SOFTWARE	70	internal software error		#
#		EX_OSERR	71	system error (e.g., can't fork)	#
#		EX_OSFILE	72	critical OS file missing	#
#		EX_CANTCREAT	73	can't create (user) output file	#
#		EX_IOERR	74	input/output error		#
#		EX_TEMPFAIL	75	temp failure; user is invited	#
#					to retry			#
#		EX_PROTOCOL	76	remote error in protocol	#
#		EX_NOPERM	77	permission denied		#
#		EX_CONFIG	78	configuration error		#
#	User-defined (here) exit codes range 79 - 113:			#
#		None							#
#									#
# Further Info:								#
#									#
#########################################################################


##################
# Init variables #
##################

readonly version=1.1.5			# Script version
readonly packageversion=1.2.2	# Package version

concurrency=""
readonly conffile=~/.kern-bld.conf	# Conf file for this script
install=false
kernconfigfile=""			# Full path to Kernel config file
new=false
objectdir=""
persist=false
sourcedir=""
update=false

currentdir=$(pwd)
readonly currentdir


#############
# Functions #
#############

# -h --help output.
# No parameters
# No return value
usage()
{
cat << EOF
Usage is:-
${0##*/} [-c] [-jX] [{-n|-u}] [-o] [-p] [-s] DIRECTORY_ABOVE_BASE
${0##*/} {-h|-V}
Usage is:-
${0##*/} [OPTIONS] DIRECTORY_ABOVE_BASE
	-c or --config-file config-filename
		Full path to the kernel config file to be copied in. Only valid
		with -n --new option
		Can be persisted with -p.
	-h or --help
		Displays usage information.
	-i or --install
		Install the built kernel.
	-j or --jobs
		Number of concurrent processes to use.
		Can be persisted with -p.
	-n or --new
		Build a kernel using a new copy of a config file.
	-o or --object-dir object-directory
		Parallel build base directory for object files.
		Can be persisted with -p.
	-p or --persist
		Saves command line options for future use.
	-s or --source-dir source-directory
		Base directory for source.
		Can be persisted with -p.
	-u or --update
		Build a kernel using the in-place config file.
	-V or --version
		Displays version information.
EOF
}

# Standard function to emit messages depending on various parameters.
# Parameters -	$1 What:-	The message to emit.
#		$2 Where:-	stdout == 0
#				stderr == 1
# No return value.
output()
{
	if (( !$2 )); then
		printf "%s\n" "$1"
	else
		printf "%s\n" "$1" 1>&2
	fi
}

# Standard function to tidy up and return exit code.
# Parameters - 	$1 is the exit code.
# No return value.
script_exit()
{
	# shellcheck disable=SC2164	# We are exiting anyway.
	cd "$currentdir"
	exit "$1"
}

# Standard function to test command error and exit if non-zero.
# Parameters - 	$1 is the exit code, (normally $? from the preceeding command).
# No return value.
std_cmd_err_handler()
{
	if (( $1 )); then
		script_exit "$1"
	fi
}

# Standard trap exit function.
# No parameters.
# No return value.
# shellcheck disable=SC2317	# Do not warn about unreachable commands in trap
# functions, they are legitimate.
trap_exit()
{
	local -i exit_code=$?
	local msg

	msg="Script terminating with exit code $exit_code due to trap received."
	output "$msg" 1
	script_exit "$exit_code"
}

# Setup trap.
trap trap_exit SIGHUP SIGINT SIGQUIT SIGTERM

# Function to (over)write config file.
# No parameters.
# No return value.
write_file()
{
	printf "sourcedir=%s\n" "$sourcedir" >$conffile
	std_cmd_err_handler $?
	printf "objectdir=%s\n" "$objectdir" >>$conffile
	std_cmd_err_handler $?
	printf "kernconfigfile=%s\n" "$kernconfigfile" >>$conffile
	std_cmd_err_handler $?
	printf "concurrency=%s\n" "$concurrency" >>$conffile
	std_cmd_err_handler $?
}

# If config file exists, read it.
# No parameters.
# No return value.
proc_config_file()
{
	local old_IFS

	if [[ -f $conffile ]]; then
		if [[ ! -r $conffile || ! -w $conffile ]]; then
			output "Incorrect permissions for config file." 1
			script_exit 77
		fi

		old_IFS=$IFS
		IFS="="
		exec 3<$conffile
		while read -u3 -ra input; do
			case ${input[0]} in
			sourcedir)
				sourcedir=${input[1]}
				;;
			objectdir)
				objectdir=${input[1]}
				;;
			kernconfigfile)
				kernconfigfile=${input[1]}
				;;
			concurrency)
				concurrency=${input[1]}
				;;
			esac
		done
		exec 3<&-
		IFS=$old_IFS
	fi
}

# Process command line arguments with GNU getopt.
# Parameters -	$1 is the command line.
# No return value.
proc_CL()
{
	local GETOPTTEMP
	local tmpGETOPTTEMP

	tmpGETOPTTEMP="getopt -o c:hij:no:ps:uV "
	tmpGETOPTTEMP+="--long config-file:,help,install,jobs:,new,object-dir:,"
	tmpGETOPTTEMP+="persist,source-dir:,update,version"
	GETOPTTEMP=$($tmpGETOPTTEMP -n "$0" -- "$@")
	std_cmd_err_handler $?

	eval set -- "$GETOPTTEMP"
	std_cmd_err_handler $?

	while true; do
		case "$1" in
		-c|--config-file)
			kernconfigfile=$2
			shift 2
			;;
		-h|--help)
			usage
			shift
			script_exit 0
			;;
		-i|--install)
			install=true
			shift
			;;
		-j|--jobs)
			concurrency=$2
			shift 2
			;;
		-n|--new)
			new=true
			shift
			;;
		-o|--object-dir)
			objectdir=$2
			shift 2
			;;
		-p|--persist)
			persist=true
			shift
			;;
		-s|--source-dir)
			sourcedir=$2
			shift 2
			;;
		-u|--update)
			update=true
			shift
			;;
		-V|--version)
			printf "%s Script version %s\n" "$0" $version
			printf "%s Package version %s\n" "$0" $packageversion
			shift
			script_exit 0
			;;
		--)	shift
			break
			;;
		*)	output "Internal error." 1
			script_exit 64
			;;
		esac
	done

	# Script must have one more argument, the directory above base level.
	if (( ! $# )); then
		output "Directory above base must be specified." 1
		script_exit 64
	fi

	# This is the directory above source and build directories.
	abovebasedir=$1
	shift

	# Script does not accept other arguments.
	if (( $# )); then
		output "Script does not accept further arguments: $1" 1
		script_exit 64
	fi

	# n and u flags cannot both be set.
	if $new && $update; then
		output "-n and -u cannot both be set." 1
		script_exit 64
	fi
}

# Check argument validity.
# No parameters.
# No return value.
check_args()
{
	local msg

	if [[ ! -d "$sourcedir/$abovebasedir" ]]; then
		msg="Source directory not valid - $sourcedir/$abovebasedir"
		output "$msg" 1
		script_exit 66
	fi

	if [[ ! -r "$sourcedir/$abovebasedir" ]]; then
		msg="Source directory not readable - $sourcedir/$abovebasedir"
		output "$msg" 1
		script_exit 77
	fi

	if [[ ! -f "$kernconfigfile" ]]; then
		output "Kernel config file not valid." 1
		script_exit 66
	fi

	if [[ ! -r "$kernconfigfile" ]]; then
		output "Kernel config file not readable." 1
		script_exit 77
	fi
}

# Prepare a new config file.
# No parameters.
# No return value.
new_config()
{
	local -i status

	# Clean
	output "Performing make distclean..." 0
	make O="$objectdir/$abovebasedir" distclean
	status=$?
	output "make distclean completed with status "$status $status
	std_cmd_err_handler $status

	# Copy config file
	output "Copy in kernel .config file..." 0
	cp -v "$kernconfigfile" "$objectdir/$abovebasedir/.config"
	status=$?
	output "Copy completed with status "$status $status
	std_cmd_err_handler $status

	# Amend config file parameters as required
	sed -ri '/CONFIG_SYSTEM_TRUSTED_KEYS/s/=.+/=""/g' \
		"$objectdir/$abovebasedir/.config"
	std_cmd_err_handler $?
	sed -ri '/CONFIG_STAGING=/s/=.+/=n/g' "$objectdir/$abovebasedir/.config"
	std_cmd_err_handler $?

	# olddefconfig
	output "Performing make olddefconfig..." 0
	make O="$objectdir/$abovebasedir" olddefconfig
	status=$?
	output "make olddefconfig completed with status "$status $status
	std_cmd_err_handler $status

	# prepare
	output "Performing make prepare..." 0
	make O="$objectdir/$abovebasedir" prepare
	status=$?
	output "make prepare completed with status "$status $status
	std_cmd_err_handler $status

	# LSMOD
	output "Prepare lsmod input..." 0
	lsmod > "$objectdir/mylsmod.txt"
	status=$?
	output "lsmod output capture completed with status "$status $status
	std_cmd_err_handler $status
	output "Performing make localmodconfig..." 0
	make O="$objectdir/$abovebasedir" LSMOD="$objectdir/mylsmod.txt" \
		localmodconfig
	status=$?
	output "make localmodconfig completed with status "$status $status
	std_cmd_err_handler $status
	output "Remove lsmod input file..." 0
	rm -v "$objectdir/mylsmod.txt"
	status=$?
	output "lsmod input file removal completed with status "$status $status
	std_cmd_err_handler $status
}

# Update an existing config file.
# No parameters.
# No return value.
update_config()
{
	local -i status

	# olddefconfig
	output "Performing make olddefconfig..." 0
	make O="$objectdir/$abovebasedir" olddefconfig
	status=$?
	output "make olddefconfig completed with status "$status $status
	std_cmd_err_handler $status

	# prepare
	output "Performing make prepare..." 0
	make O="$objectdir/$abovebasedir" prepare
	status=$?
	output "make prepare completed with status "$status $status
	std_cmd_err_handler $status
}


# Make menuconfig and make.
# No parameters.
# No return value.
perform_build()
{
	local -i status

	# menuconfig
	output "Performing make menuconfig..." 0
	make O="$objectdir/$abovebasedir" menuconfig
	status=$?
	output "make menuconfig completed with status "$status $status
	std_cmd_err_handler $status

	# Actual make
	output "Performing make -j$concurrency..." 0
	make O="$objectdir/$abovebasedir" -j"$concurrency"
	status=$?
	output "make -j$concurrency completed with status "$status $status
	std_cmd_err_handler $status
}

# Perform installation.
# No parameters.
# No return value.
perform_install()
{
	local -i status

	########################
	# Whilst the following code snippet "works", the target locations are
	# questionable, as is the link from /lib/nmodules.
	#
	# output "Performing kernel header install..." 0
	# sudo make headers_install \
	#		INSTALL_HDR_PATH=/usr/src/linux-headers-4.10.0
	# status=$?
	# std_cmd_err_handler $status
	# sudo mkdir -p /lib/modules/4.10.0
	# status=$?
	# std_cmd_err_handler $status
	# sudo ln -s /usr/src/linux-headers-4.10.0 /lib/modules/4.10.0/build
	# status=$?
	# output "Kernel header install completed with status "$status $status
	# std_cmd_err_handler $status
	########################


	output "Performing kernel install..." 0
	sudo make O="$objectdir/$abovebasedir" modules_install install
	status=$?
	output "Kernel install completed with status "$status $status
	std_cmd_err_handler $status
}


########
# Main #
########

proc_config_file

proc_CL "$@"

check_args

# If persist is true, save parameters.
if $persist; then
	write_file
fi

# Now perform the requests
# shellcheck disable=SC2164	# We are error checking anyway.
cd "$sourcedir/$abovebasedir"
std_cmd_err_handler $?

if $new; then
	new_config
fi

if $update; then
	update_config
fi

if $new || $update; then
	perform_build
fi

if $install; then
	perform_install
fi

# And exit
output "Script complete with exit code: 0" 0
script_exit 0

