#!/usr/bin/bash
#
# A convenience script for ReadyKernel.
#
# Copyright (c) 2016-2021 Virtuozzo International GmbH
#

if [[ $EUID -ne 0 ]]; then
	echo "The command requires root privileges"
	exit 1
fi

COMMAND=$0
INSTALLDIR=/var/lib/kpatch
PATCHBASENAME=readykernel-patch
KERNELVER=$(uname -r)
PATCHPACKAGE=$PATCHBASENAME-$KERNELVER

BASEURL="${RK_API_BASEURL:-https://readykernel.com}"
APIURL="${BASEURL%/}/api/v1"

CRONDIR=/etc/cron.d

source /etc/os-release
DISTRO=$ID

# utils
KPATCHBIN=/usr/sbin/kpatch
if [[ -x /var/lib/rk-rhel/sbin/vzsubscribe ]]; then
	VZSUBSCRIBEBIN=/var/lib/rk-rhel/sbin/vzsubscribe
elif [[ -x /var/lib/rk-rhel/bin/vzsubscribe ]]; then
	VZSUBSCRIBEBIN=/var/lib/rk-rhel/bin/vzsubscribe
elif [[ -x /usr/libexec/vzsubscribe ]]; then
	VZSUBSCRIBEBIN=/usr/libexec/vzsubscribe
elif [[ -x /usr/sbin/vzsubscribe ]]; then
	VZSUBSCRIBEBIN=/usr/sbin/vzsubscribe
else
	VZSUBSCRIBEBIN=/usr/bin/vzsubscribe
fi

DNFBIN=/usr/bin/dnf
YUMBIN=/usr/bin/yum
APTBIN=/usr/bin/apt-get

PKG_TYPE=""
if [ -x $DNFBIN ] ; then
	SKIP_DNF_PLUGINS=""
	# The systems based on RHEL 8+ may provide their kpatch-specific
	# plugin for dnf.
	if [ -e /etc/dnf/plugins/kpatch.conf ] ; then
		SKIP_DNF_PLUGINS="--disableplugin=kpatch"
	fi

	INSTALL_CMD="$DNFBIN ${SKIP_DNF_PLUGINS} install -y -q"
	REMOVE_CMD="$DNFBIN ${SKIP_DNF_PLUGINS} remove -y"
	MODE="dnf"
	PKG_TYPE="rpm"
	PKG_MGR=$DNFBIN
elif [ -x $YUMBIN ] ; then
	SKIP_YUM_PLUGINS=""
	for plug in openvz virtuozzo hci-release; do
		if [ -e /etc/yum/pluginconf.d/${plug}.conf ] ; then
			SKIP_YUM_PLUGINS="$SKIP_YUM_PLUGINS --disableplugin=${plug}"
		fi
	done

	INSTALL_CMD="$YUMBIN ${SKIP_YUM_PLUGINS} install -y -q"
	REMOVE_CMD="$YUMBIN ${SKIP_YUM_PLUGINS} remove -y"
	MODE="yum"
	PKG_TYPE="rpm"
	PKG_MGR=$YUMBIN
elif [ -x $APTBIN ] ; then
	export DEBIAN_FRONTEND=noninteractive
	INSTALL_CMD="$APTBIN install -y"
	REMOVE_CMD="$APTBIN remove -y"
	MODE="apt"
	PKG_TYPE="deb"
	PKG_MGR=$APTBIN
else
	echo "Unable to find supported package management tools."
	exit 1
fi

# Depending on the kernel version and configuration, the information about
# the loaded RK patches can be located in different directories in sysfs.
if [[ -e /sys/kernel/kpatch/patches ]] ; then
	# kpatch < 0.4
	SYSFS="/sys/kernel/kpatch/patches"
elif [[ -e /sys/kernel/kpatch ]] ; then
	# kpatch >= 0.4
	SYSFS="/sys/kernel/kpatch"
else
	# assuming Livepatch is used
	SYSFS="/sys/kernel/livepatch"
fi

REPOQUERYBIN=/usr/bin/repoquery
LOGDIR=/var/log/readykernel
LOGFILE=$LOGDIR/readykernel.log
LOGLASTFILE=$LOGDIR/last.log

ERRORS=()
LOGS=()

export PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

check_executable() {
	[[ -x $1 ]] && return
	echo "error: $1 is not found" >&2
	return 1
}

# Some distros may come with their own 'kpatch' package preinstalled.
# has_native_kpatch() returns true (ret code 0) for those, false (ret code 1)
# otherwise.
has_native_kpatch () {
	case ${DISTRO} in
	rhel|centos|alma*|rocky*)
		rpm -q kpatch > /dev/null 2>&1 && return 0
		;;
	virtuozzo)
		# VzLinux might have RedHat's kpatch, other VZ distros do not use that
		if [[ "${CPE_NAME}" =~ vzlinux ]]; then
			rpm -q kpatch > /dev/null 2>&1 && return 0
		fi
		;;
	ubuntu|debian)
		dpkg -s kpatch > /dev/null 2>&1 && return 0
		;;
	esac

	# no "native" kpatch
	return 1
}

__find_module () {
	MODULE="$1"
	[[ -f "$MODULE" ]] && return

	MODULE=$INSTALLDIR/$(uname -r)/"$1"
	[[ -f "$MODULE" ]] && return

	return 1
}

mod_name () {
	MODNAME="$(basename $1)"
	MODNAME="${MODNAME%.ko}"
	MODNAME="${MODNAME//-/_}"
}

find_module () {
	arg="$1"
	if [[ "$arg" =~ \.ko ]]; then
		__find_module "$arg" || return 1
		mod_name "$MODULE"
		return
	else
		for i in $INSTALLDIR/$(uname -r)/*; do
			mod_name "$i"
			if [[ $MODNAME == $arg ]]; then
				MODULE="$i"
				return
			fi
		done
	fi

	return 1
}

installed_package_name() {
	local PKG_NAME
	if [[ ${PKG_TYPE} == "rpm" ]]; then
		PKG_NAME=$(rpm -q --whatprovides "$PATCHPACKAGE" --qf "%{NAME}\n" 2>/dev/null | head -n 1; exit ${PIPESTATUS[0]})
	else
		PKG_NAME=$(apt list --installed "$PATCHPACKAGE" 2>/dev/null | grep "$PATCHPACKAGE" | cut -f1 -d/ | head -n 1; exit ${PIPESTATUS[1]})
	fi

	[[ $? != 0 ]] && return 1

	echo "$PKG_NAME"
}

mod_file()
{
	local PKG_NAME MOD_FILE
	PKG_NAME=$(installed_package_name)

	[[ $? != 0 ]] && return 1

	if [[ ${PKG_TYPE} == "rpm" ]]; then
		MOD_FILE=$(rpm -ql "$PKG_NAME" 2>/dev/null | grep -m 1 \.ko; exit ${PIPESTATUS[0]})
	else
		MOD_FILE=$(dpkg -L "$PKG_NAME" 2>/dev/null | grep -m 1 \.ko; exit ${PIPESTATUS[0]})
	fi
	[[ $? = 0 ]] && [[ -n "$MOD_FILE" ]] && echo $MOD_FILE
}

# This is NOT a protection against a malicious user, it is too easy to disable.
# check_license() only makes it clearer why operations with RK patches fail,
# if they fail due to a missing or invalid license.
check_license() {
	# TODO: re-enable license checking after licensing policy for
	# non-VHS distros has been decided on and implemented in the
	# protected package repositories.
	# See VZP-1373 and related tasks (VZP-1500, VZP-1640, etc.).
	return 0

	check_executable $VZSUBSCRIBEBIN || exit 1
	if [[ ! "$($VZSUBSCRIBEBIN info 2>/dev/null)" =~ "system successfully registered" && -e /etc/virtuozzo-release ]]; then
		$VZSUBSCRIBEBIN r
	fi
	[[ "$($VZSUBSCRIBEBIN info 2>/dev/null)" =~ "system successfully registered" ]]
}

usage_cmd() {
	printf '   %-20s\n      %s\n' "$1" "$2"
}

print_inactive_usage() {
	usage_cmd "initialize [--remove-patches-before-install]" "Initialize ReadyKernel interactively"
	usage_cmd "licinfo" "Show information about the installed license"
	usage_cmd "licload [--accept-eula] <KEY>" "Install the license key <KEY>"
	usage_cmd "licunload" "Remove the license"
	echo
	usage_cmd "report" "Create a report for the technical support team"
	usage_cmd "help" "Show help on command usage"
}

print_usage() {
	echo "Usage: $COMMAND ACTION [OPTIONS]"
	echo "Supported actions are:"
	usage_cmd "info" "Show the current ReadyKernel status"
	usage_cmd "check-update" "Check for a newer ReadyKernel patch for the current kernel"
	usage_cmd "update" "Install the latest ReadyKernel patches"
	usage_cmd "autoupdate [enable|disable] [<HOUR>]" "Enable or disable automatic installation of the latest ReadyKernel patches. If enabled, the service will check for updates daily at the specified hour set in 24-hour format, server time."
	echo
	usage_cmd "load" "Load the latest installed ReadyKernel patch"
	usage_cmd "autoload [enable|disable]" "Enable or disable automatic loading of the latest installed ReadyKernel patches"
	usage_cmd "load-replace" "Unload all the kernel patches (ReadyKernel and other), then load the latest installed ReadyKernel patch"
	usage_cmd "patch-info" "Show information about the loaded ReadyKernel patch"
	usage_cmd "unload" "Unload the ReadyKernel patch"
	echo
	print_inactive_usage
}

print_no_license() {
	print_error "The system is not yet registered. Use '$COMMAND licload <KEY>' to register." >&2
}

usage() {
	print_usage >&2
}

print_newest_patch_name() {
	local PATCHDIR
	PATCHDIR="$SYSFS"
	[[ -d $PATCHDIR ]] || return

	for mod in $PATCHDIR/*; do echo $(basename "$mod"); done \
		| grep kpatch_cumulative | sort -V -r | head -n 1
}

kpatch_list() {
	[[ -x $KPATCHBIN ]] || echo "No patch has been loaded" && return 0
	$KPATCHBIN list
}

kpatch_load() {
	local PATCH
	PATCH=$(print_newest_patch_name)
	if [[ -n "$PATCH" ]]; then
		echo "The patch $PATCH is already loaded"
		return 1
	fi

	PATCH=$(mod_file)
	if [[ -z "$PATCH" ]]; then
		print_error "No patch has been found" >&2
		return 1
	fi

	if [[ -e /snap/bin/canonical-livepatch ]]; then
		print_error "Canonical-livepatch detected, will not load the patch" >&2
		return 1
	fi
	check_executable $KPATCHBIN || return 1
	$KPATCHBIN load "$PATCH"
}

kpatch_unload() {
	local PATCH
	PATCH=$(print_newest_patch_name)
	if [[ -z "$PATCH" ]]; then
		echo "No patch has been loaded"
		return
	fi

	check_executable $KPATCHBIN || return 1
	$KPATCHBIN unload "$PATCH"
}

load_replace() {
	local PATCH
	PATCH=$(mod_file)
	if [[ -z "$PATCH" ]]; then
		print_error "No patch has been found" >&2
		return 1
	fi

	check_executable $KPATCHBIN || return 1
	$KPATCHBIN replace "$PATCH"
}

kpatch_info() {
	local PATCH
	PATCH=$(print_newest_patch_name)
	if [[ -z "$PATCH" ]]; then
		echo No patch has been loaded
		return
	fi

	check_executable $KPATCHBIN || return 1
	$KPATCHBIN info "$PATCH"
}

vzsubscribe_register() {
	check_executable $VZSUBSCRIBEBIN || exit 1
	"$VZSUBSCRIBEBIN" register "$@"
}

vzsubscribe_unregister() {
	check_executable $VZSUBSCRIBEBIN || exit 1
	$VZSUBSCRIBEBIN unregister
}

vzsubscribe_info() {
	local l rv=1
	check_executable $VZSUBSCRIBEBIN || exit 1
	while read l; do
		[[ ! $l =~ "system successfully registered" ]] || rv=0
		printf '%s\n' "$l"
	done < <($VZSUBSCRIBEBIN info)
	if (( rv == 1 )) && [ -e /etc/virtuozzo-release ]; then
		$VZSUBSCRIBEBIN r
		while read l; do
			[[ ! $l =~ "system successfully registered" ]] || rv=0
			printf '%s\n' "$l"
		done < <($VZSUBSCRIBEBIN info)
	fi
	(( rv == 0 )) || print_no_license
	return 0  # as per VZP-924
}

clean_repo_rpm() {
	[[ -x $REPOQUERYBIN ]] || return

	local PKG_NAME REPO_NAME

	PKG_NAME=$(installed_package_name)
	[[ $? != 0 ]] && return 1

	REPO_NAME=$($REPOQUERYBIN --qf "%{repoid}" "$PKG_NAME" 2>/dev/null)

	[[ $? = 0 ]] && [[ -n "$REPO_NAME" ]] && $PKG_MGR clean all --disablerepo="*" \
		--enablerepo="$REPO_NAME" >/dev/null 2>&1
}

clean_repo_deb() {
#	apt-get update -o Dir::Etc::sourcelist="/etc/apt/sources.list.d/readykernel.list" >/dev/null 2>&1
#	apt-get update -o Dir::Etc::sourcelist="/etc/apt/sources.list.d/subscription.list" >/dev/null 2>&1
	apt-get update >/dev/null 2>&1
}

install_package() {
	local retval
	local pkg

	pkg=$1
	[[ -z $(check_provides $pkg) ]] && print_error "No package $pkg available" >&2 && return 1
	log_command $INSTALL_CMD $pkg
	retval=$?
	[[ $retval != 0 ]] && print_error "Failed to install the new package (package manager error)" >&2
	return $retval
}

update_patches() {
	check_license || { print_no_license; return 1; }
	clean_repo_${PKG_TYPE}

	local RESULT=0
	local CURRENTKERNELVER=$(uname -r)
	local RET=0
	unset SKIP_KERNEL_VER_CHECK
	for KERNELVER in `ls -d /lib/modules/*/ | xargs -n1 basename`; do
		[[ -d "/lib/modules/$KERNELVER/kernel" ]] || continue

		echo "Checking for patch updates for kernel $KERNELVER..."
		if [[ -z "$(check_provides $PATCHBASENAME-$KERNELVER)" ]]; then
			echo "No ReadyKernel patches are available for the kernel $KERNELVER."
			[[ $KERNELVER = $CURRENTKERNELVER ]] && RESULT=1
			echo
			continue
		fi

		install_package $PATCHBASENAME-$KERNELVER
		if [[ $? != 0 ]]; then
			RESULT=1
		fi
		echo
	done
	return $RESULT
}

# Check if there is an installed but not loaded official patch
# for the current kernel and, if so, try to load it.
load_missing_patches()
{
	check_executable $KPATCHBIN || exit 1

	local CURRENTKERNELVER=$(uname -r)
	for mod in $INSTALLDIR/$CURRENTKERNELVER/kpatch-cumulative-*.ko; do
		[[ -f "$mod" ]] || continue
		MODNAME=""
		mod_name $mod

		# If the directory for the module is present in $SYSFS,
		# the module is loaded and it is a kernel patch module.
		[[ -d "$SYSFS/$MODNAME" ]] && continue

		echo "Kernel patch module $MODNAME is installed but not loaded."
		echo "Attempting to load it..."
		$KPATCHBIN replace "$MODNAME"
		# Loading might fail if the relevant kernel functions are
		# in use. If so, just go on.

		# We expect no more that one official patch module for the
		# current kernel to be present .
		break
	done
}

auto_update()
{
	if [[ -z "$1" ]]; then
		[[ -f $CRONDIR/readykernel ]] && echo "Automatic updating is enabled" && return
		echo "Automatic updating is disabled" && return
	fi

	case "$1" in
	"enable"|"--enable")
		local HOUR

		if [[ -z "$2" ]]; then
			HOUR=12
		elif [[ "$2" =~ ^[0-9]+$ ]] && [ "$2" -ge 0 -a "$2" -le 23 ]; then
			HOUR=$(($2/1))
		else
			print_error "Wrong hour value. Specify an hour value from 0 to 23" >&2 && return 1
		fi

		local MINUTE=$(( $RANDOM % 16 ))
		local CRONTIME="$MINUTE $HOUR * * * root"
		if echo "$CRONTIME $(realpath $COMMAND) update; /bin/cp -f $LOGLASTFILE $LOGDIR/autoupdate.log" > $CRONDIR/readykernel; then
			printf "Automatic updating has been scheduled for %02d:%02d daily (server time)" $HOUR $MINUTE; echo
		else
			print_error "failed to enable automatic updating" >&2
			return 1
		fi
		;;

	"disable"|"--disable")
		if rm -f $CRONDIR/readykernel; then
			echo "Automatic updating has been disabled"
		else
			print_error "Failed to disable automatic updating" >&2
			return 1
		fi
		;;

	*)
		echo "Unknown command: autoupdate $1"
		usage
		return 1
		;;
	esac
}

auto_load() {
	[[ -z "$1" ]] && print_error "Too few arguments" >&2 && usage && return 1

	local MOD_FILE
	MOD_FILE=$(mod_file)

	[[ -z "$MOD_FILE" ]] && echo No patches have been installed && return
	mod_name "$MOD_FILE"

	case "$1" in
	"enable"|"--enable")
		$KPATCHBIN install "$MOD_FILE"
		return $?
		;;

	"disable"|"--disable")
		$KPATCHBIN uninstall "$MODNAME"
		return $?
		;;

	*)
		echo "Unknown command: autoload $1"
		usage
		return 1
		;;
	esac
}

# Usage: check_provides <patch-package-or-provide_name>
check_provides() {
	[[ -z "$1" ]] && return 1

	if [[ $MODE == "dnf" ]] || [[ $MODE == "yum" ]]; then
		log_command $PKG_MGR provides -q $1
	else
		log_command apt-cache search $1
	fi
}

check_update() {
	check_license || { print_no_license; return 1; }
	
	clean_repo_${PKG_TYPE}
	local PKG_NAME AVAILABLE UPDATES
	PKG_NAME=$(installed_package_name)

	if [[ $? != 0 ]]; then
		# No patch has been installed yet. Nothing to update.
		if [[ $MODE == "dnf" ]] || [[ $MODE == "yum" ]]; then
			AVAILABLE=$(check_provides $PATCHPACKAGE | \
			grep $PATCHBASENAME | tail -n 2 | head -n 1 | cut -f1 -d ":"; exit ${PIPESTATUS[0]})
		else
			AVAILABLE=$(check_provides $PATCHPACKAGE | cut -f1 -d\  ; exit ${PIPESTATUS[0]})
		fi

		[[ $? != 0 ]] && print_error "Failed to find available packages." >&2 && return 1

		[[ -n $AVAILABLE ]] && echo A new patch is available: && echo "$AVAILABLE"
		return
	fi

	if [[ $MODE == "dnf" ]] || [[ $MODE == "yum" ]]; then
		UPDATES=$($PKG_MGR check-update -q $PKG_NAME)
	else
		UPDATES=$(apt list --upgradable $PKG_NAME 2>/dev/null | grep $PKG_NAME ; exit ${PIPESTATUS[0]})
	fi

	[[ $? = 1 ]] && print_error "Failed to check updates ($MODE error)" >&2 && return 1

	if [[ -z "$UPDATES" ]]; then
		echo No updates are available
	else
		echo A new version is available:
		echo "$UPDATES"
	fi
}

create_report() {
	check_executable /usr/bin/zip || return 1

	local TMPREPORT PATCH ZIPFILE
	TMPREPORT=$(mktemp -d)
	[[ $? != 0 ]] && echo Error: Unable to create tmp directory >&2 && return 1

	check_executable $VZSUBSCRIBEBIN && (cd $TMPREPORT; $VZSUBSCRIBEBIN diagnostics >/dev/null)

	print_info >$TMPREPORT/readykernel_info.txt 2>&1 
	$KPATCHBIN list >$TMPREPORT/kpatch_list.txt 2>&1 
	cp /var/log/kpatch.log $TMPREPORT/

	cp -r /var/log/readykernel $TMPREPORT >/dev/null 2>&1

	PATCH=$(print_newest_patch_name)
	[[ -n "$PATCH" ]] && $KPATCHBIN info "$PATCH" >$TMPDIR/kpatch_info.txt 2>&1

	ZIPFILE=$(realpath readykernel-report-$(date +%Y-%m-%d-%H-%M-%S))
	(cd $TMPREPORT; /usr/bin/zip -r $ZIPFILE  *)

	echo The report file $ZIPFILE.zip has been created
	rm -rf "$TMPREPORT"
}

initialize_interactive() {
	local LIC_KEY
	local AUTOUPDATE=X
	local REMOVE_KPATCH

	if has_native_kpatch; then
		REMOVE_KPATCH=
		[[ "t$1" == "t--remove-patches-before-install" ]] && REMOVE_KPATCH=y
		echo
		echo "Another live patching system detected: 'kpatch' package is installed. It may be unsafe to use both that system and ReadyKernel at the same time."
		while [[ -t 0 ]] && ! [[ "t$REMOVE_KPATCH" =~ ^t(y|Y|n|N)$ ]]; do
			echo
			read -n 1 -p $' > Do you want to remove that package so that ReadyKernel updates could be installed? (Y/N) ' REMOVE_KPATCH
		done
		echo

		if [[ $REMOVE_KPATCH =~ ^(y|Y)$ ]]; then
			echo "Trying to remove 'kpatch'."
			$REMOVE_CMD kpatch || return 1
		else
			echo "ReadyKernel updates cannot be used safely if other live patching tools for the kernel are present in the system. If you would like to use ReadyKernel, please remove other live patching tools first."
			return 1
		fi
	fi

	if ! check_license; then
		[[ -t 0 ]] && read -t 60 -n 50 -p $' > Enter license key: ' LIC_KEY

		echo
		if [[ -n "$LIC_KEY" ]]; then
			vzsubscribe_register "$LIC_KEY"
			[[ $? != 0 ]] && echo "Initialization has been cancelled" && return 1
		else
			echo "You cannot continue without a license key."
			return 1
		fi
	else
		echo
		echo " > The system is already registered."
	fi

	while [[ -t 0 ]] && [[ -n $AUTOUPDATE ]] && ! [[ $AUTOUPDATE =~ ^(y|Y|n|N)$ ]]; do
		echo
		AUTOUPDATE=y
		read -t 60 -n 1 -p $' > Do you want to enable automatic updating? (Y/n) ' AUTOUPDATE
	done

	[[ -n $AUTOUPDATE ]] && echo

	if [[ -z $AUTOUPDATE ]] || [[ $AUTOUPDATE =~ ^(y|Y)$ ]]; then
		auto_update "enable"
	else
		auto_update "disable"
	fi

	echo
	echo " > Patch installation has been initiated."
	echo
	update_patches && echo && print_info

	if [[ -e /snap/bin/canonical-livepatch ]]; then
		print_error "Canonical-livepatch detected; it is highly recommended to remove it, it is dangerous to have ReadyKernel and LivePatch together" >&2
	fi

}

unset MODULE

[[ "$#" -lt 1 ]] && usage && exit 1

print_info() {
	PATCH=$(print_newest_patch_name)
	if [[ -z "$PATCH" ]]; then
		NR_PATCHES=$(ls -ld $SYSFS/kpatch* 2>/dev/null | wc -l)
		echo "Loaded patches: $NR_PATCHES."
		return 1
	fi

	VERREL_RAW=$(echo $PATCH | sed 's/^kpatch_cumulative_//;s/_/\./g')
	PATCHVER=$(echo $VERREL_RAW | sed 's/\.r.*$//')
	VERREL=$(echo $VERREL_RAW | sed 's/\.r/-/')

	if [[ $MODE == "yum" ]]; then
		PKG_NAME=$(rpm -q --whatprovides $PATCHPACKAGE --qf "%{NAME}-%{VERSION}-%{RELEASE}\n" 2>/dev/null | head -n 1; exit ${PIPESTATUS[0]})
	else
		PKG_NAME=$(installed_package_name)-${VERREL}
	fi

	[[ $? = 0 ]] && echo "Patch name: $PKG_NAME"

	if find_module "$PATCH"; then
		echo "Patch module: $PATCH"
		echo -n "File: "
		modinfo -F "filename" "$MODULE" || die "failed to get info for module $PATCH"
	else
		echo "Patch module $PATCH is loaded but not installed."
	fi

	echo "Version: $PATCHVER"

	echo
	INFO_FILE=/usr/share/readykernel-patch-$(uname -r)/info-$VERREL.txt
	if [[ -f "$INFO_FILE" ]]; then
		cat "$INFO_FILE"
	else
		INFO_FILE=/usr/share/kpatch-patch-$(uname -r)/info-$VERREL.txt
		[[ -f "$INFO_FILE" ]] && cat "$INFO_FILE"
	fi
}

check_argc() {
	[[ "$1" -le "$2" ]] && return 0
	print_error "Too many arguments" >&2 && usage && return 1
}

print_date() {
	date "+%Y-%m-%d %H:%M:%S"
}

print_error() {
	ERRORS+=("$1")
	echo "Error: $1"
}

log_command() {
	local COMMAND RESULT
	local TEMPLOG

	TEMPLOG=$(mktemp)
	if [[ -z "$TEMPLOG" ]]; then
		$@ 2>/dev/null
		return $?
	fi

	COMMAND=$@
	$@ 2>$TEMPLOG
	RESULT=$?

	if [[ $RESULT -ne 0 ]]; then
		LOGS+=( "call: $COMMAND" "return: $RESULT" "stderr:" )
		IFS=$'\n'
		LOGS+=($(cat $TEMPLOG))
		unset IFS
	fi

	rm $TEMPLOG
	return $RESULT
}

COMMANDLOG=$@
LOGS=("date: `print_date`" "cmd: $COMMANDLOG")

SUBCOMMAND="$1"
shift
case "${SUBCOMMAND}" in
"info"|"--info")
	check_argc $# 0 || exit 1

	print_info
	;;

"list"|"-l"|"--list")
	check_argc $# 0 || exit 1

	kpatch_list
	;;

"load"|"--load")
	check_argc $# 0 || exit 1

	kpatch_load
	;;

"load-replace"|"--load-replace")
	check_argc $# 0 || exit 1

	load_replace
	;;

"unload"|"--unload")
	check_argc $# 0 || exit 1

	kpatch_unload
	;;

"patch-info"|"--patch-info")
	check_argc $# 0 || exit 1

	kpatch_info
	;;

"check-update"|"--check-update")
	check_argc $# 0 || exit 1

	check_update
	;;

"licinfo"|"--licinfo")
	check_argc $# 0 || exit 1

	vzsubscribe_info
	;;

"licload"|"--licload")
	check_argc $# 2 || exit 1

	vzsubscribe_register "$@"
	;;

"licunload"|"--licunload")
	check_argc $# 0 || exit 1

	vzsubscribe_unregister
	;;

"report"|"--report")
	check_argc $# 0 || exit 1

	create_report
	;;

"update"|"--update")
	check_argc $# 0 || exit 1

	# Just in case someone skips 'readykernel init'.
	if has_native_kpatch; then
		print_error "Another live patching system detected: 'kpatch' package is installed. It may be unsafe to use both that system and ReadyKernel at the same time." >&2
		exit 1
	fi

	update_patches
	RESULT=$?
	load_missing_patches
	exit $RESULT
	;;

"autoupdate"|"--autoupdate")
	check_argc $# 2 || exit 1

	auto_update "$@"
	;;

"autoload"|"--autoload")
	check_argc $# 1 || exit 1

	auto_load "$@"
	;;

"init"|"initialize"|"--initialize")
	check_argc $# 1 || exit 1

	initialize_interactive "$@"
	;;

"help"|"-h"|"--help")
	check_argc $# 0 || exit 1

	print_usage
	;;

*)
	echo "Unknown command: \"$1\""
	usage
	exit 1
esac

RESULT=$?

LOGS+=( "exit: $RESULT" "errors:" )
mkdir -p "${LOGDIR}"
printf "%s\n" "${LOGS[@]}" > $LOGLASTFILE
[[ ${#ERRORS[@]} != 0 ]] && printf "%s\n" "${ERRORS[@]}" >> $LOGLASTFILE
cat $LOGLASTFILE >> $LOGFILE

exit $RESULT
