#!/bin/sh
#
#	Update cron entry
#
#	Changelog:
#
#		Tue Dec 21 2010 - Guillaume Catto
# 		* Fix crms00284851: script creation
#
#               The Jan 24 2013 - Jerry ZHOU
#               * PR261951: Reduce time of unavailability during boot time
# authorized options
#	-d: set day of the month
#	-f: set temporary input file (if set, current cron options are not modified)
#	-m: set month
#	-t: set time in format hh:mm
#	-w: set day of the week
#
#	-c: only check parameters without applying modifications
#	-h: display some help
#	-p: add some debug
#	-r: remove cron entry only

unset cron_day_month
unset cron_month
unset cron_time
unset cron_day_week
unset cron_tmp_output
unset cron_bin

cron_file="/var/spool/cron/crontabs/${USER}"
cron_remove=0
cron_debug=0
cron_check=0

# define_aliases
#---------------
# ARGS:
#   <none>  define macros that are used in the different functions
define_aliases() {
    local -
    set +x

    esc=$'\x1b'
    attr="$esc[7m"
    errorattr="$esc[31;7m"
    default="$esc[0m"
    ROOT=${ROOT:-}
    HOME_ADMIN_SYS="/home/admin"
    HOME_ADMIN_UPG="/system/home/admin"

    alias DEFAULT_DEBUG_OFF="local ldebug=1"
    alias defPS4="PS4=\${PS4_TPLT:-\"\$esc[\$((\$\$%6+31))m+(\$\$)\$FUNCTION:\$esc[0m \"}"
    alias IN_DEBUG="([[ -f $HOME_ADMIN_SYS/.debug.upgrade ]] || [[ -f $HOME_ADMIN_UPG/.debug.upgrade ]])"
    alias IN_NODEBUG="([[ -f $HOME_ADMIN_SYS/.nodebug.upgrade ]] || [[ -f $HOME_ADMIN_UPG/.nodebug.upgrade ]])"
#PR261951 jerryzh+
    if [[ -f $HOME_ADMIN_SYS/.debug.upgrade ]] || [[ -f $HOME_ADMIN_UPG/.debug.upgrade ]]; then
    	alias ENTER_FUNCTION="
    	[[ \${ldebug:-0} -eq 1 ]] && { local -; set +x; }
    	: \"---- ENTER_FUNCTION\"
    	: \"\$esc[4mfrom \${EF_function:-unknown()}\${default}\"
    	local OIFS=\$IFS IFS=\",\"
    	local EF_function=\"\$FUNCTION(\"\"\$*\"\")\"
    	IFS=\$OIFS
    	local _in_xflag=\${-//[^x]}
    	local PS4
    	defPS4
    	if IN_DEBUG; then
    	    local -; set -x
    	fi
    	if IN_NODEBUG; then
    	    local -; set +x
    	fi
    	(IFS=\",\"; : \"\${attr}START \$EF_function\${default}\")
    	: \"---- ENTER_FUNCTION END\"
    	"
# CRMS00312748 END
#   NOTE:
#       - RETURN macro in a short 'if' has to be like
#           [[ -f <file> ]] && { RETURN <ret_code>; }
#         The '{' and '; }' are VERY VERY important
#       - The ret_code has to be an immediate or a variable other than "$?".
#         "$?" is destroyed by the RETURN macro.
    	alias RETURN="
    	: \"\${attr}LEAVING \$EF_function\${default}\"
    	if [[ \"\$_in_xflag\" == \"\${-//[^x]}\" ]]; then
    	    [[ -n \"\$_in_xflag\" ]] && set -x || set +x
    	fi
    	return"
    else
        alias ENTER_FUNCTION=""
        alias RETURN="return"
    fi
#PR261951 jerryzh-
}
define_aliases

usage() {
	echo "Usage: cronupdt [-d <day_of_the_month>] [-m <month>] [-t <time_hh:mm>] [-w <day_of_the_week>]"
	echo "				  [-f <intput_file>] [-h] [-p] [-r] <binary_path>"
}

get_current_time() {
	local curtime=$(date +%R)
	local FUNCTION=get_current_time
	ENTER_FUNCTION

	: Split current time ${curtime} into hour/minutes

	cur_hour=${curtime%%:*}
	cur_min=${curtime##*:}

	RETURN 0
}

need_cron_time_update() {
	local FUNCTION=need_cron_time_update
	ENTER_FUNCTION

	: Check current time to new one
	get_current_time

	if [[ ${cur_hour} == ${cron_hour} ]]; then
		if [[ ${cron_min} < ${cur_min} && ${cron_min} >= $(expr ${cur_min} - 2) ]]; then
			cron_min=$(expr ${cron_min} + 2)
		fi
	fi

	RETURN 0
}

set_cron_time() {
	local FUNCTION=set_cron_time
	ENTER_FUNCTION

	: Split time ${cron_time} into hours/minutes
	cron_hour=${cron_time%%:*}
	cron_min=${cron_time##*:}
	: Hours ${cron_hour}, minutes ${cron_min}
	RETURN 0
}

dump_file_without_line() {
	local line_bin=
	local line_nbr=0
	local FUNCTION=dump_file_without_line
	ENTER_FUNCTION

	user=${cron_file##*/}
	cron_tmp_output=/tmp/cron.${user}.new

	: Dumping current cron configuration without element ${cron_bin}
	[[ -f ${cron_tmp_output} ]] && rm -f ${cron_tmp_output}

	while read line; do
		: Skipping line that begins with "#"
		if [[ ! -z "${line##\#*}" ]]; then
			: Parsing line "${line}"
			line_bin=$(echo "${line}" | cut -d" " -f 6)
			: Binary found ${line_bin}
			[[ "${line_bin}" == "${cron_bin}" ]] && continue
		fi
		echo "${line}" >> ${cron_tmp_output}
		line_nbr=$(expr ${line_nbr} + 1)
	done < ${cron_file}

	RETURN ${line_nbr}
}

write_new_line_to_file() {
	local new_line=
	local FUNCTION=write_new_line_to_file
	ENTER_FUNCTION
	

	new_line="${cron_min-"*"} ${cron_hour-"*"} ${cron_day_month-"*"} ${cron_month-"*"} ${cron_day_week-"*"} ${cron_args}"
	: New configuration line "${new_line}"
	echo "${new_line}" >> ${cron_tmp_output}
	RETURN
}

is_digit() {
	local FUNCTION=is_digit
	ENTER_FUNCTION

	case "$1" in
		[[:digit:]]*) return 1 ;;
		*) return 0;;
	esac

	RETURN
}

is_in_limits() {
	local FUNCTION=is_in_limits
	ENTER_FUNCTION

	: Check if "$1" is a digit
	is_digit "$1"
	[[ $? != 1 ]] && return 0

	: "Validate if $2 < $1 < $3"
	[ $1 -lt $2 ] || [ $1 -gt $3 ] && { RETURN 0; }
	RETURN 1
}

is_all_digit() {
	local substring=
	local nextsubstring=$1
	local is_star=0
	local FUNCTION=is_all_digit
	ENTER_FUNCTION

	until [[ "${substring}" == "${nextsubstring}" ]]; do
		substring=${nextsubstring%%[\-\,\/]*}
		if [[ "$substring" != "*" ]]; then
			is_in_limits "$substring" $2 $3
			[[ $? != 1 ]] && { RETURN 0; }
		else
			[[ $is_star == 1 ]] && { RETURN 0; }
			is_star=1
		fi
		nextsubstring=${nextsubstring#*[\-\,\/]}
	done

	RETURN 1
}

is_week_day() {
	local FUNCTION=is_week_day
	ENTER_FUNCTION

	case "$1" in
		"mon"|"tue"|"wed"|"thu"|"fri"|"sat"|"sun") { RETURN 1; } ;;
		*) { RETURN 0; } ;;
	esac
}

is_week_days() {
	local substring=
	local nextsubstring=$1
	local is_star=0
	local FUNCTION=is_week_days
	ENTER_FUNCTION

	until [[ "${substring}" == "${nextsubstring}" ]]; do
		substring=${nextsubstring%%[\-\,\/]*}
		if [[ "$substring" != "*" ]]; then
			is_week_day $substring
			[[ $? != 1 ]] && return 0
		else
			[[ $is_star == 1 ]] && return 0
			is_star=1
		fi
		nextsubstring=${nextsubstring#*[\-\,\/]}
	done

	RETURN 1
}

validate_cron_options() {
	local ret=1
	local FUNCTION=validate_cron_options
	ENTER_FUNCTION

	if [[ ! -z "$cron_day_month" ]]; then
		is_all_digit $cron_day_month 1 31
		[[ $? != 1 ]] && echo "Invalid entry ${cron_day_month}" && ret=0
	fi
	if [[ ! -z "$cron_hour" ]]; then
		is_all_digit $cron_hour 0 23
		[[ $? != 1 ]] && echo "Invalid entry ${cron_hour}" && ret=0
	fi
	if [[ ! -z "$cron_min" ]]; then
		is_all_digit $cron_min 0 59
		[[ $? != 1 ]] && echo "Invalid entry ${cron_min}" && ret=0
	fi
	if [[ ! -z "$cron_month" ]]; then
		is_all_digit $cron_month 1 12
		[[ $? != 1 ]] && echo "Invalid entry ${cron_month}" && ret=0
	fi
	if [[ ! -z "$cron_day_week" ]]; then
		is_all_digit $cron_day_week 0 6
		if [[ $? != 1 ]]; then
			is_week_days $cron_day_week
			[[ $? != 1 ]] && echo "Invalid entry ${cron_month}" && ret=0
		fi
	fi

	RETURN $ret
}

: Parse input args
while getopts "d:f:m:t:w:chpr" flags; do
	: Treating option ${flags}
	case ${flags} in
		"c") cron_check=1 ;;
		"d") cron_day_month=$OPTARG ;;
		"f") cron_file=$OPTARG ;;
		"h") usage; exit 0 ;;
		"m") cron_month=$OPTARG ;;
		"p") set -x ;;
		"r") cron_remove=1 ;;
		"t") cron_time=$OPTARG ;;
		"w") cron_day_week=$OPTARG ;;
		"?") echo "Unrecognized option $OPTARG"; retval=1; break ;;
		":") echo "Missing parameter to option $OPTARG"; retval=1; break;;
	esac
done
shift $(($OPTIND - 1))

: Find cron binary
[[ $# == 0 ]] && echo "Missing last argument" && exit 1
cron_bin=$1
cron_args=$@
retval=1


: Validate option coherency
if [[ ${cron_remove} == 0 && -z "${cron_time}" && -z "${cron_day_month}" && -z "${cron_day_week}" ]]; then
	echo "Invalid cron syntax, there should be, at least, a start time configured"
	exit 2
fi

: Computing all options
if [[ ! -z "$cron_time" ]]; then
	set_cron_time
	retval=$?
	[[ $retval != 0 ]] && exit $retval
	need_cron_time_update
	[[ $retval != 0 ]] && exit $retval
fi

: Extract modifications from original file
if [[ ! -f $cron_file ]]; then
	logger -t upgrade -p local0.WARNING "No cron configuration file found !"
	exit 1
fi
dump_file_without_line

if [[ ${cron_remove} == 0 ]]; then
	echo "Adding new entry for ${cron_bin} in cron configuration"
	: Checking cron options coherency
	validate_cron_options
	retval=$?
	[[ $retval != 1 ]] && exit 3

	: Adding new line to new configuration file
	write_new_line_to_file
	retval=$?
else
	echo "Removing entry ${cron_bin} from cron configuration"
fi

# In case we are just checking the modifications
# stop here
[[ $cron_check == 1 ]] && exit $retval

# Update cron configuration with new file
crontab ${cron_tmp_output}
[ $? -eq 0 ] && logger -t upgrade -p local0.INFO "cron configuration has been modified" || logger -t upgrade -p local0.ERR "Failed to update cron configuration"

exit $retval

