#!/bin/bash
#set -x

newline=$'\n'
tab=$'\t'
#IFS=$newline

################### Support interactive selection of timezone name ######
# Set the continent global variable according to reply
set_continent(){
    select continent in \
	Africa \
	Americas \
	Antarctica \
	'Arctic Ocean' \
	Asia \
	'Atlantic Ocean' \
	Australia \
	Europe \
	'Indian Ocean' \
	'Pacific Ocean'
      do
      case $continent in
	  ('')
	  echo 'Please enter a number in range.';;
	  (?*)
	  case $continent in
	      (Americas) continent=America;;
	      (*' '*) continent=${continent%% *} ;; # Strip off space onwards
	  esac
	  break
      esac
    done
    case $continent in
	'')
	    exit 1;;
    esac
}

#set_posix(){
#	# Ask the user for a POSIX TZ string.  Check that it conforms.
#	while
#	echo 'Please enter the desired value of the TZ environment variable.'
#	echo 'For example, '
#	echo 'GST-10 is a zone named GST that is 10 hours ahead (east) of UTC.'
#	read TZ
#	$AWK -v TZ="$TZ" 'BEGIN {
#				tzname = "[^-+,0-9][^-+,0-9][^-+,0-9]+"
#				time = "[0-2]?[0-9](:[0-5][0-9](:[0-5][0-9])?)?"
#				offset = "[-+]?" time
#				date = "(J?[0-9]+|M[0-9]+\.[0-9]+\.[0-9]+)"
#				datetime = "," date "(/" time ")?"
#				tzpattern = "^(:.*|" tzname offset "(" tzname \
#				  "(" offset ")?(" datetime datetime ")?)?)$"
#				if (TZ ~ tzpattern) exit 1
#				exit 0
#			}'
#	  do
#	  echo "\`$TZ' is not a conforming POSIX time zone string."
#	done
#	TZ_for_date=$TZ
#}

# get a list of valid posix timezones, set TZ_for_date to it.
set_posix(){
    cd "$TZDIR"
    shopt -s extglob
    set -- "[A-Z][A-Z][A-Z]?(?([+-])+([0-9])?([A-Z][A-Z][A-Z]))"
    select TZ in $@
      do
      case $TZ in
	  ('') echo 'Please enter a number in range.';;
	  (?*) break
      esac
    done
    case $TZ in
	'') exit 1 ;;
    esac
    TZ_for_date=$TZ
}

# return a newline delimited list of countries in continent $1
get_countries(){
    # get sed to write a script for itself
    sed_script=$(/bin/sed -n '/^#/d;s=^\(..\)'"$tab[^$tab]*$tab$1/.*=s/^\\1$tab//p=p" \
	$TZ_ZONE_TABLE)
    /bin/sed -n "$sed_script" $TZ_COUNTRY_TABLE | /bin/sort -u
}

set_country(){
# If there's more than one country, ask the user which one.
    case $countries in
	(*"$newline"*)
	echo >&2 'Please select a country.'
	select country in $countries
	  do
	  case $country in
			  ('') echo >&2 'Please enter a number in range.';;
	      (?*) break
	  esac
	done
	
	case $country in
	    '') exit 1
	esac;;
	(*)
	country=$countries
    esac
}

# return a newline delimited list of timezone regions for country $1
get_regions(){
    cc=$(/bin/sed -n "/^#/d;/$tab$1\$/{ s///p; q;}" $TZ_COUNTRY_TABLE)
    /bin/sed -n "s/^$cc$tab[^$tab]*$tab[^$tab]*$tab//p" $TZ_ZONE_TABLE
}

set_region(){
	# If there's more than one region, ask the user which one.
    case "$@" in
	(*"$newline"*)
	echo >&2 'Please select one of the following' \
	    'time zone regions.'
	select region in $@
	  do
	  case $region in
	      ('') echo >&2 'Please enter a number in range.';;
	      (?*) break
	  esac
	done
	case $region in
	    '') exit 1
	esac;;
	(*)
	region=$regions
    esac
}

set_TZ_from_region(){
    cc=$(/bin/sed -n "/^#/d;/$tab$1\$/{ s///p; q;}" $TZ_COUNTRY_TABLE)
	# Use : rather than / in these scripts as some regions (Greenland) have /
	# in their name
    if [ -z "$2" ]
	then
	TZ=$(/bin/sed -n "s:^$cc$tab[^$tab]*$tab\\([^$tab]*\\)\$:\\1:p" $TZ_ZONE_TABLE)
    else
	TZ=$(/bin/sed -n "s:^$cc$tab[^$tab]*$tab\\([^$tab]*\\)$tab$2\$:\\1:p" $TZ_ZONE_TABLE)
    fi
}

interactive_selection_loop(){
    exec 3>&1 1>&2
    IFS="$newline"
    PS3='Enter number or control-D to quit ?'
# Begin the main loop.  We come back here if the user wants to retry.
    while
    echo 'Please identify a location' \
	'so that time zone rules can be set correctly.'

    continent=
    country=
    region=

    echo 'Please select a continent or ocean.'

    set_continent
    case "$continent" in
	(*)
		# Get list of names of countries in the continent or ocean.
	countries=$(get_countries $continent)
	set_country "$countries"

		# Get list of names of time zone rule regions in the country.
	regions=$(get_regions "$country")
	set_region "$regions"
	set_TZ_from_region "$country" "$region"
		# Make sure the corresponding zoneinfo file exists.
	TZ_for_date=$TZDIR/${TZ:-wrong}
	validTZname "${TZ}"
	<$TZ_for_date || {
	    echo >&2 "tsTimeZone: time zone files are not set up correctly"
	    exit 1
	}
    esac

	# Use the proposed TZ to output the current date relative to UTC.
	# Loop until they agree in seconds.
	# Give up after 8 unsuccessful tries.

    IFS=$' \t\n'
    extra_info=
    for i in 1 2 3 4 5 6 7 8
      do
      TZdate=$(LANG=C TZ="$TZ_for_date" /bin/date)
      UTdate=$(LANG=C TZ=UTC0 /bin/date)
      TZsec=$(/usr/bin/expr "$TZdate" : '.*:\([0-5][0-9]\)')
      UTsec=$(/usr/bin/expr "$UTdate" : '.*:\([0-5][0-9]\)')
      case $TZsec in
	  ($UTsec)
	  extra_info="
Local time is now:	$TZdate.
Universal Time is now:	$UTdate."
	  break
      esac
    done


	# Output TZ info and ask the user to confirm.

    echo ""
    echo "The following information has been given:"
    echo ""
    case $country+$region in
	(?*+?*)	echo "	$country$newline	$region";;
	(?*+)	echo "	$country";;
	(+)	echo "	TZ='$TZ'"
    esac
    echo ""
    echo "Therefore TZ='$TZ' will be used.$extra_info"
    echo "Is the above information OK?"

    ok=
    select ok in Yes No
      do
      case $ok in
	  ('') echo 'Please enter 1 for Yes, or 2 for No.';;
	  (?*) break
      esac
    done
    case $ok in
	('') exit 1;;
	Yes) break
    esac
      do :
      IFS="$newline"
    done

    echo >&3 "$TZ"
}

do_interactive(){
    if TZ=$(interactive_selection_loop)
	then
	setTZName "$TZ"
    fi
}

############## End of interactive support routines

# return true if in newmode 
# Side effect set the tzh/tzm or the zonename variables
newmode()
{
    if [ -z "$in_newmode" ]
    then
	in_newmode=1
	if zonename=$(/fabos/cliexec/config get ts.tz 5)
	then
	    in_newmode=0
	else
	    tzh=$(/fabos/cliexec/config get ts.tzh 2)
	    tzm=$(/fabos/cliexec/config get ts.tzm 2)
	fi
    fi
    return $in_newmode
}


rsh_helper()
{
	/bin/ping -q -W 2 -c 1 ${1} > /dev/null
	retval=$?
	if [ $retval -eq 0 ]; then
		/usr/bin/rsh ${1} /fabos/bin/date_sync
	fi
}


update_extn_dp_tz_info() {
	sw_platform=$( /sbin/sin 2>/dev/null | /bin/sed -n 's/^.*SWBD\([^,]*\),.*/\1/p' )
	if [ $sw_platform -eq 178 ] || [ $sw_platform -eq 148 ] || [ $sw_platform -eq 171 ]; then
		rsh_helper dp0 > /dev/null 2>&1
		if [ $sw_platform -eq 148 ] || [ $sw_platform -eq 171 ]; then
			rsh_helper dp1 > /dev/null 2>&1
		fi
	elif [ $sw_platform -eq 165 ] || [ $sw_platform -eq 166 ]; then
		for blade in `/usr/bin/seq 3 12`; do
		if [ -f /proc/fabos/blade/${blade}/info ]; then
			bladeid=`/bin/cat /proc/fabos/blade/${blade}/info | /bin/grep --binary-files=text bladeId | /bin/sed 's/[^0-9]//g'`
			if [ "$bladeid" == "186" ]; then
				rsh_helper sl${blade}_dp0 > /dev/null 2>&1
				rsh_helper sl${blade}_dp1 > /dev/null 2>&1
			fi
		fi
		done
	fi
}

# Do the time zone link creation if the link doesn't exist already 
make_tz_link(){
   local L=$(/usr/bin/find ${LOCALTIME} -lname ${TZDIR}/$1 -print 2>/dev/null)
   [ -n "$L" ] && return
   /bin/ln -sf "${TZDIR}/$1" ${LOCALTIME}
}

# Do the actual work
setTZName(){
    /fabos/cliexec/config set ts.tz 5 "$1"
    /fabos/cliexec/config set ts.tz 5 "$1"
    validTZname "$1"
    make_tz_link "$1"
    # Update DP tz in the background to give prompt back to user quickly
    update_extn_dp_tz_info &
}

usage(){
    echo "Usage: tsTimeZone [ --interactive | timezone ] " >&2
    echo "tsTimeZone [--old] hours [,] [minutes]" >&2
    exit 2
}

# If called with the old syntax, then check that we are in old mode
# and set the timezone if valid
old_syntax(){
    if newmode
	then
        exec >&2
	# The old format is hr [[,] min] with various options on whitespace
	# hr | hr, | hr , | hr, mn | hr , mn
	echo "This command now requires a timezone name, rather than an offset"
	echo "Try running with \"--interactive\" to get a menu system to choose"
	echo "To revert to the old system use tsTimeZone --old hours minutes"
	echo "The timezone is currently '$zonename'"
	return 1
    fi
    set_old_format old2old "$@"
}

# conversion and validation routine for old time offsets
# given arguements hours mins, returns name of zone and 0
# or "" and 1
getTZname(){
    case "$2" in
	(0)  printf "Etc/GMT%+d" $((- $1 ));;
	(-30)
	case $1 in
	    (-3) printf "Canada/Newfoundland" ;;
	    (-9) printf "Pacific/Marquesas";;
	    (-8) printf "UTC" ;;
	    (*) return 1;;
	esac ;;
	(30)
	case $1 in
	    (3) printf "Asia/Tehran" ;;
	    (4) printf "Asia/Kabul" ;;
	    (5) printf "Asia/Calcutta" ;;
	    (6) printf "Asia/Rangoon" ;;
	    (9) printf "Australia/Adelaide" ;;
	    (10) printf "Australia/Lord_Howe" ;;
	    (11) printf "Pacific/Norfolk" ;;
	    (*) return 1;;
	esac ;;
	(*) return 1 ;;
    esac
    return 0
}

# Actually do downgrade
downgrader()
{
    old2old "$@"
    old2old "$@"
    /fabos/cliexec/config remove ts.tz
    /fabos/cliexec/config remove ts.tz
}

# Already in old mode, stay there
old2old()
{
    /fabos/cliexec/config set ts.tzm 2 $2
    /fabos/cliexec/config set ts.tzh 2 $1
    if validTZname "$3" ; then
		make_tz_link "$3"
    fi
}

invalid_old()
{
    echo "Invalid offset value specified '$@'" >&2
    exit 1
}

shopt -s extglob
# set the old format timezone. used both for normal old->old and downgrade
# new->old
set_old_format()
{
    action=$1 ; shift
    # expression matching a 1 or 2 digit number with optional leading sign
    # '?([+-])[0-9]?([0-9])'
    declare -i hours min=0
    case $# in
	(1) # "h" or "h," or "h,m"
	case $1 in
	    (?([+-])[0-9]?([0-9])?(,)) hours=${1%,};;
	    (?([+-])[0-9]?([0-9])?(,)?([+-])[0-9]?([0-9]))
	    hours=${1%,*} min=${1#*,} ;;
	    (*) invalid_old "$@";;
	esac ;;
	(2) # "h m" or "h, m"
	case "$1 $2" in
	    (?([+-])[0-9]?([0-9])?(,)\ ?([+-])[0-9]?([0-9]))
	    hours=${1%,} min=$2 ;;
	    (*) invalid_old "$@";;
	esac ;;
	(3) # "h , m"
	case "$1 $2 $3" in
	    (?([+-])[0-9]?([0-9])" , "?([+-])[0-9]?([0-9]))
	    hours=${1} min=$3 ;;
	    (*) invalid_old "$@";;
	esac ;;
	(*) invalid_old "$@" ;;
    esac
    if [[ $hours -lt -12 || $hours -gt 12 ]] ; then
	invalid_old "$@"
    fi
    if zonename=$(getTZname $hours $min)
	then
	# we have a valid zone. call the action routine
	$action $hours $min "$zonename"
    else
	invalid_old "$@"
    fi
}

# convert from new format to old format
downgrade()
{
    shift # loose the --old arguement
    set_old_format downgrader "$@"
}

getTZinfo()
{
    if newmode
	then
	echo "Timezone $zonename"
    else
	echo "Time Zone Hour Offset: ${tzh-0}"
	echo "Time Zone Hour Offset: ${tzm-0}"
	echo "Please note Switch 0 Time Zone is the System Time Zone"
    fi
}

shopt -s extglob

setup()
{
    : ${TZDIR=/usr/share/zoneinfo} ${LOCALTIME=/etc/localtime}

# Read two tables
# TZ_COUNTRY_TABLE: code \t name
# TZ_ZONE_TABLE: code \t lat/log \t zone \t comments
# Make sure the tables are readable.
    TZ_COUNTRY_TABLE=$TZDIR/iso3166.tab
    TZ_ZONE_TABLE=$TZDIR/zone.tab
    for f in $TZ_COUNTRY_TABLE $TZ_ZONE_TABLE
      do
      <$f || {
	  echo >&2 "tsTimeZone: time zone files are not set up correctly"
	  exit 1
      }
    done
}

validTZname()
{
	no_fields=`echo $1 | /bin/awk -F'/' '{print NF}'`
	no_fields=`/usr/bin/expr $no_fields - 1`

	if [ $no_fields -eq 1 ]
	  then
	  slotport_matched=`echo $1 | /bin/sed 's/[A-Za-z0-9_+-]\{1,\}\/[A-Za-z0-9_+-]\{1,\}//'`
	else
	  slotport_matched=`echo $1 | /bin/sed 's/[A-Za-z0-9_+-]\{1,\}\/[A-Za-z0-9_+-]\{1,\}\/[A-Za-z0-9_+-]\{1,\}//'`
	fi

	# In case of UTC, above sed is returning UTC as value and not NULL. 
	# hence if condition succeds and we are anot able to add the timezone. 
	# Added a condition to handle this scenario 

	if [ "$slotport_matched"  == "UTC" ]; then
        slotport_matched=""
    fi
 
    if [ $slotport_matched ]
    	then
    	echo "Invalid timezone" >&2
    	usage
    	return 1
    fi

    [ -f ${TZDIR}/"$1" ] && [ -r ${TZDIR}/"$1" ] && return 0
    [ -f ${TZDIR}/allzone.tgz ] && (
	cd ${TZDIR}
	local linkinfo=$(/bin/zcat allzone.tgz | /bin/tar vtf - "$1" 2> /dev/null)
	case ${linkinfo} in
		(*" link to "*)
			/bin/zcat allzone.tgz | /bin/tar xf - "$1" "${linkinfo##*link to }" ;;
		(-*) 
			/bin/zcat allzone.tgz | /bin/tar xf - "$1" ;;
	esac
    )
    [ -f ${TZDIR}/"$1" ] && [ -r ${TZDIR}/"$1" ] && return 0
    return 1
}

printTZ(){
    if newmode
    then
	echo "$zonename"
    else
	echo "Time Zone Hour Offset:" ${tzh-0}
	echo "Time Zone Minute Offset:" ${tzm-0}
    fi
}

allowed()
{
    if /fabos/cliexec/hashow |
            /bin/grep -w Standby | /bin/grep -w Local > /dev/null; then
	return 0
	fi
# if we are not in the user login context then set us the admin domain
    if [ -z "$LOGNAME" ]
    then
        export CURRENT_AD=255
	if [ $ROLE_ID != "remotedomain" ]; then
        	export ROLE_ID=root
	fi
        export CHASSIS_ROLEID=0
        /fabos/libexec/getDefaultFID
        export CURRENT_VF=$?
    fi

	[ -x /fabos/libexec/rbac_check ] || return 0
	if /fabos/libexec/rbac_check tsTimeZone $1
	then
		return 0
	fi
#	echo "Permission Denied" >&2
	return 1
}

changed_message()
{
    # Force to non-cached state
    unset in_newmode
    if [ "$oldtzstate" != "$(printTZ)" ]
    then
	/fabos/cliexec/config update
    	newzone=$(/fabos/cliexec/config get ts.tz 5)
	if [ ! $newzone ]; then
    	    newzone_hr=$(/fabos/cliexec/config get ts.tzh 5)
		if [ $newzone_hr ]; then
		   newzone_min=$(/fabos/cliexec/config get ts.tzm 5)
          	   if [[ ${newzone_hr:0:1} == "-" ]] ; then
	  		newzone="GMT$newzone_hr:$newzone_min"
	  	   else
		 	newzone="GMT+$newzone_hr:$newzone_min"
		   fi
		fi
	fi
	export ROLE_ID="$1"
	/fabos/libexec/secnotify "time_cfg" "$oldzone" "$newzone"
	echo "System Time Zone change will take effect at next reboot. On Chassis, reboot both CPs simultaneously."
    fi
}

shopt -s extglob
main()
{
	setup
	export FABOS_SWITCHNO=0;
        rolename=`echo $ROLE_ID`
	if [ $# -ge 1 ]; then
    	   oldzone=$(/fabos/cliexec/config get ts.tz 5)
	   if [ ! $oldzone ]; then
    	    oldzone_hr=$(/fabos/cliexec/config get ts.tzh 5)
		if [ $oldzone_hr ]; then
		   oldzone_min=$(/fabos/cliexec/config get ts.tzm 5)
          	   if [[ ${oldzone_hr:0:1} == "-" ]] ; then
	  		oldzone="GMT$oldzone_hr:$oldzone_min"
	  	   else
		   	oldzone="GMT+$oldzone_hr:$oldzone_min"
		   fi
		fi
   	   fi
	fi
		
# if allowed is called without a paramter then it is a show command
    oldtzstate="$(printTZ)"
    case "$#:$1" in
	(0:) allowed && echo "$oldtzstate" ; return ;;
	(1:-i|1:--interactive) allowed change && do_interactive ;;
	([123]:?([+-])[0-9]*) allowed change && old_syntax "$@";;
	([234]:--old) allowed change && downgrade "$@" ;;
	(1:*)	allowed change && if validTZname "$1"
		    		  then
				    setTZName "$1" ;
				  else
				    echo "Invalid timezone" >&2
	 			    usage
		               	  fi ;;
	(*) usage ;;
    esac
    changed_message "$rolename"
}

#exec 4>>/tmp/timelog
#date -u >&4
#echo "$FABOS_SWITCHNO:Args $@" >&4

if [ "$1" != "--unittest" ] ; then main "$@" ; fi
