#!/bin/sh

# A simple wrapper for kpatch utility that retries the command if the 
# functions to be patched are currently running.
# 
# The arguments are the same as for kpatch utility.

# How many times to retry, at most.
NRETRIES=5

# How long to sleep before retrying, in seconds.
SLEEP_INTERVAL=10

CMD="/usr/sbin/kpatch $@"
TMP_FILE=/tmp/kpatch-with-retries.tmp

LOGGER_CMD="logger -p notice -t kpatch-with-retries"
MSG_BUSY="Failed to process the patch module: the functions to be patched are currently running. Will retry after a delay..."

LC_ALL=C $CMD > ${TMP_FILE} 2>&1
EXIT_CODE=$?
cat ${TMP_FILE}

for i in $(seq 1 ${NRETRIES}); do
	[[ ${EXIT_CODE} -eq 0 ]] && break
	
	# Unfortunately, insmod always returns 1 if loading of a module 
	# fails, no matter which error has caused that. As a result, the 
	# exit code of kpatch utility and of the commands it executes 
	# cannot be used to find the reason of the failure. We have to
	# check the output messages for that.
	grep 'Device or resource busy' ${TMP_FILE} > /dev/null || break

	echo "${MSG_BUSY}"
	${LOGGER_CMD} "${MSG_BUSY}"

	sleep $SLEEP_INTERVAL
	LC_ALL=C $CMD > ${TMP_FILE} 2>&1
	EXIT_CODE=$?
	cat ${TMP_FILE}
done

if [[ ${EXIT_CODE} -ne 0 ]]; then
	FAILMSG=$(cat ${TMP_FILE} | grep -E "kpatch: (can|failed)" | head -n 1)
	[[ -n "$FAILMSG" ]] && ${LOGGER_CMD} "${FAILMSG}"
fi

rm -f ${TMP_FILE}
exit ${EXIT_CODE}
