#!/usr/bin/bash
#
# A convenience script for ReadyKernel.
#
# Copyright (c) 2016-2026, Virtuozzo International GmbH, All rights reserved
#

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

SYSFS="/sys/kernel/livepatch"

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" "Initialize ReadyKernel interactively"
	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 (atomically replaces any previously loaded patch)"
	usage_cmd "autoload [enable|disable]" "Enable or disable automatic loading of the latest installed ReadyKernel patches"
	usage_cmd "patch-info" "Show information about the loaded ReadyKernel patch"
	usage_cmd "unload [--force]" "Unload the ReadyKernel patch (not recommended, may be unsafe)"
	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() {
	if [[ ! -x $KPATCHBIN ]]; then
		echo "No patch has been loaded"
		return 0
	fi
	$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 force 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 load "$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 load "$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

	if has_native_kpatch; then
		print_error "Another live patching system detected: 'kpatch' package is installed. Please remove it before using ReadyKernel." >&2
		return 1
	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")
	echo "The 'load-replace' command has been removed."
	echo "Use 'load' instead - it atomically replaces any previously loaded patch."
	exit 1
	;;

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

	if [[ "$1" != "--force" ]]; then
		echo "WARNING: unloading a live patch is not recommended and may be unsafe." >&2
		if [[ -t 0 ]]; then
			read -n 1 -p "Continue? [y/N] " ANSWER
			echo
			[[ "$ANSWER" =~ ^[yY]$ ]] || exit 1
		else
			echo "Use '--force' to unload non-interactively." >&2
			exit 1
		fi
	fi

	kpatch_unload
	;;

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

	kpatch_info
	;;

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

	check_update
	;;

"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
