#!/usr/bin/ksh

#
# Copyright 2003-2004 Sun Microsystems, Inc.  All rights reserved.
# Use is subject to license terms.
#
#ident	"@(#)ipmpgroupmanager.sh	1.3	04/07/26 SMI"
#
# An helper script called from MBean operations to perform IPMP Group related
# operations.
#
# Input parameters:
# - operation type: add, del, edit
# - group name
# - adapter name
# - IP version: inet or inet6
# - test address
# - flag standby or -standby
# - persistent flag: update hostname file required or not
#
# Return value:
# - 0 if successful
# - 1 otherwise
#

# Readonly variables
typeset -r AWK="/usr/bin/awk"
typeset -r CAT="/usr/bin/cat"
typeset -r CP="/usr/bin/cp"
typeset -r DIFF="/usr/bin/diff"
typeset -r GREP="/usr/xpg4/bin/grep"
typeset -r IFCONFIG="/usr/sbin/ifconfig"
typeset -r IFPARSE="/sbin/ifparse"
typeset -r RM="/bin/rm"
typeset -r SED="/usr/bin/sed"
typeset -r TOUCH="/usr/bin/touch"
typeset -r WC="/usr/bin/wc"

# Parameters
typeset adapter
typeset group
typeset inet
integer persistent
typeset standby
typeset test

# Internal
integer -r SUCCESS=0
integer -r FAILURE=1
integer -r CMD_ECHO_ON=1
integer -r CMD_ECHO_OFF=0
typeset dest_file
typeset file
typeset test_addr
typeset test_list

#
# common_exit helper
#
# Note that all functions return ${SUCCESS} if successful, 1 otherwise.
#
# Input parameter
# - return code
# - msg
#
end()
{
    integer rc=${1}
    typeset msg="${2}"
    typeset err

    # Cleanup
    [ -f "${dest_file}" ] && err=`${RM} -f "${dest_file}" 2>/dev/null`
    if [ ${?} -ne 0 ]
    then
        msg="${err}\n${2}"
        rc=${FAILURE}
    fi

    # Send message to stderr, only works with ksh because of print
    [ ${rc} -ne 0 ] && print -u2 "${msg}"

    exit ${rc}
}

#
# run_cmd helper for running command
#
# Input parameter
# - command line
# - print command line to stdout or not
#
run()
{
     typeset err

    # Command failed: print command messages to stdout
    err=`${1} 2>&1`
    [ ${?} -ne 0 ] && end 1 "${err}"

    # Command successful: print command line to stdout
    [ ${2} -eq ${CMD_ECHO_ON} ] && echo "${1};"

    return ${SUCCESS}
}

#
# get_hostname_file helper for getting the hostname file name.
#
# Called at the very beginning of operations on hostname file.
#
get_hostname_file()
{
    case "${inet}" in
        "inet")
            file=/etc/hostname.${adapter}
            ;;
        "inet6")
            file=/etc/hostname6.${adapter}
            ;;
        *)
            end ${FAILURE} "${inet}"
            ;;
    esac

    # We write into a copy of hostname file
    dest_file="${file}.tmp${$}"
    run "${TOUCH} ${dest_file}" ${CMD_ECHO_OFF}

    return ${SUCCESS}
}

#
# ifparse_hostname_file helper for parsing the hostname file.
#
# Read lines from stdin.
# Send processed line to stdout.
#
ifparse_hostname_file()
{
    typeset line
    while read line
    do
        [ -z "${line}" ] && continue
        # Do not quote $line
        ${IFPARSE} "${inet}" ${line}
        [ ${?} -ne 0 ] && end ${FAILURE} "${IFPARSE}"
    done
    return ${SUCCESS}
}

#
# replace_hostname_file helper for replacing the hostname file name.
#
# Called at the very end of operations on hostname file.
#
replace_hostname_file()
{
    # Keep a copy of the original hostname file. If that fails, keep going
    [ -f "${file}" ] && ${CP} "${file}" "${file}.old" 2>/dev/null

    # Replace hostname file while removing empty lines, etc ...
    run "${RM} -f ${file}" ${CMD_ECHO_OFF}
    run "${TOUCH} ${file}" ${CMD_ECHO_OFF}
    ifparse_hostname_file <"${dest_file}" >>"${file}"

    if [ -f "${file}" ]
    then
	# Hostname file didn't change, remove the copy if any
        if [ -f "${file}.old" ]
        then
	    ${DIFF} "${file}" "${file}.old" >/dev/null
	    [ ${?} -eq 0 ] && ${RM} -f "${file}.old" 2>/dev/null
        fi
    fi

    # If hostname file is empty, just remove it
    if [ ! -s "${file}" ]
    then
        run "${RM} -f ${file}" ${CMD_ECHO_OFF}
    fi

    return ${SUCCESS}
}

# style_hostname_file helper for converting hostname filr to new style.
#
# See /etc/init.d/network about old style:
#
#     If there is only line in an hostname file we assume it contains
#     the old style address which results in the interface being brought up 
#     and the netmask and broadcast address being set.
#
#     If there are multiple lines we assume the file contains a list of
#     commands to the processor with neither the implied bringing up of the
#     interface nor the setting of the default netmask and broadcast address.
#
# Called before adding lines to hostname file.
#
# Send processed line to stdout.
#
style_hostname_file()
{
    # Hostname file not already existing
    [ ! -f "${file}" ] && return ${SUCCESS}

    set -- `${WC} -l "${file}"`
    if [ ${1} -ne 1 ]
    then
         # Multiple lines hostname file: nothing to change
        ${CAT} "${file}"
    else
         typeset line
         # Old style
         line=`${CAT} "${file}"`
         ${GREP} "netmask" "${file}" >/dev/null 2>&1
         [ ${?} -ne 0 ] && line="${line} netmask +"
         ${GREP} "broadcast" "${file}" >/dev/null 2>&1
         [ ${?} -ne 0 ] && line="${line} broadcast +"
         ${GREP} " up" "${file}" >/dev/null 2>&1
         [ ${?} -ne 0 ] && line="${line} up"
         # Don't use print as 1st character might be -
         echo "${line}"
    fi

    return ${SUCCESS}
}

#
# standby_hostname_file helper for removing standby and -standby.
#
# A standby before the end of file would make addif fails.
#
# Called before adding addif lines to hostname file.
#
# Read lines from stdin.
# Send processed line to stdout.
#
standby_hostname_file()
{
    typeset line
    while read line
    do
        typeset out
        out=`echo "${line}" | \
            ${SED} -e /"standby"/s/"[\-]\{0,1\}standby"/""/g`
        # Don't use print as 1st character might be -
        [ -n "${out}" ] && echo "${out}"
    done
    return ${SUCCESS}
}

# get_interface_address helper to get the IP address of the interface.
# Information is sent to stdout.
#
# Input parameters
# - Interface name
# - IP version
#
get_interface_address()
{
    ${IFCONFIG} "${1}" "${2}" 2>/dev/null | ${AWK} '/inet/ { print $2 }'
    return ${SUCCESS}
}

#
# get_test_address helper to get the test address (can be several of them).
# Information is sent to stdout.
#
get_test_address()
{
    if [ "${inet}" = "inet" ]
    then
        # We filter for instance hme0:1:, hme0:2: but not hme0:
        test_list=`${IFCONFIG} -a "${inet}" | ${AWK} '/'^"${adapter}":[0-9]'/ {
            if(index($0, "DEPRECATED") != 0) {
                if(index($0, "NOFAILOVER") != 0) {
                    # Remove the last character : added by ifconfig
                    s = substr($1, 0, length($1) -1);
                    print s;
                }
            }
        }'`
    fi
    return ${SUCCESS}
}

#
# chg_test_address change the interface's test address.
# If no test address is already configured, create it.
#
chg_test_address()
{
    # If no test address is already configured, create it
    get_test_address
    if [ -z "${test_list}" ]
    then
	# If a test address is provided use it
	if [ -n "${test}" ]
	then
        	run \
        	"${IFCONFIG} ${adapter} ${inet} addif ${test} netmask + broadcast + deprecated -failover up"\
         	${CMD_ECHO_ON}
	fi

    # Change the 1st address in the list
    else
        typeset test_adp

        test_adp=`print ${test_list} | ${AWK} '{ print $1 }'`

        # Will be used for updating hostname file if persistency required
        test_addr=`get_interface_address "${test_adp}" "${inet}"`

        # Set the adapter's address
	if [ -n "${test}" ] 
	then 
		run "${IFCONFIG} ${test_adp} ${inet} ${test} netmask + broadcast +"\
		${CMD_ECHO_ON}
	else 
		run "${IFCONFIG} ${test_adp} ${inet} unplumb"\
		${CMD_ECHO_ON}
	fi
    fi
    return ${SUCCESS}
}

#
# del_test_address unconfigure the given interface's test address.
#
# Test address is related to IPMP Group configuration. It is a logical
# interface with flags deprecated and -failover set. It is used for health
# monitoring of the physical interface and trigger a failover when a failure
# is detected.
#
del_test_address()
{
    get_test_address
    for test in ${test_list}
    do
        run "${IFCONFIG} ${test} ${inet} unplumb" ${CMD_ECHO_ON}
    done
    return ${SUCCESS}
}

#
# del_adapter unplumb the adapter if it was plumbed when adding an IPMP Group.
#
del_adapter()
{
    typeset addr
    integer failed

    if [ "${inet}" = "inet6" ] 
    then
    	run "${IFCONFIG} ${adapter} ${inet} unplumb" ${CMD_ECHO_ON}
        return ${SUCCESS}
    fi

    # Get the adapter's address
    addr=`get_interface_address "${adapter}" "${inet}"`

    # Check if FAILED or OFFLINE is set
    failed=`${IFCONFIG} ${adapter} ${inet} 2>/dev/null | ${AWK} ' /flags/ {
        if(index($0, "FAILED") != 0) {
            print 1;
        } else {
            if (index($0, "OFFLINE") != 0) {
                print 1;
            } else {
                print 0;
            }
        }
    }`

    # Unplumb if adapter's address is 0.0.0.0, no logical interfaces,
    # no FAILED or OFFLINE
    [ "${addr}" = "0.0.0.0" ] && [ ${failed} -eq 0 ] && \
    run "${IFCONFIG} ${adapter} ${inet} unplumb" ${CMD_ECHO_ON}

    return ${SUCCESS}
}

#
# set_standby flag
#
set_standby()
{
    # Interface must be plumbed
    ${IFCONFIG} ${adapter} ${inet} >/dev/null 2>&1
    [ ${?} -eq 0 ] && \
        run "${IFCONFIG} ${adapter} ${inet} ${standby}" ${CMD_ECHO_ON}
    return ${SUCCESS}
}

#
# add_group_to_hostname append to a given interface's hostname file the group,
# test address and standby flag settings.
#
# IPMP Group name is added or modified if one was already specified.
# Test address is related to IPMP Group configuration. It is a logical
# interface with flags deprecated and -failover set. It is used for health
# monitoring of the physical interface and trigger a failover when a failure
# is detected.
#
add_group_to_hostname()
{
    typeset one
    typeset two
    typeset three

    get_hostname_file
    print "group ${group}" >>"${dest_file}"
    style_hostname_file | standby_hostname_file | ifparse_hostname_file | \
    while read one two three
    do
        case "${one}" in
                "group")
                    [ -n "${three}" ] && print "${three}" >>"${dest_file}"
                    ;;
                "addif")
                    if [ "${two}" = "${test}" ]
                    then
                        # Filter already existing test address
			print "${three}" | ${AWK} '{
                            if((index($0, "deprecated") == 0) ||
                                (index($0, "-failover") == 0)) {
                                print $0;
                            }
                        }' >>"${dest_file}"
                    # This is not the logical address we want to change
                    else
                        print ${one} ${two} ${three} >>"${dest_file}"
                    fi
                    ;;
                *)  
                    # Don't use print as 1st character might be a -
                    echo ${one} ${two} ${three} >>"${dest_file}"
                    ;;
        esac
    done

    # Append addif if a testaddress has been provided
    if [ -n "${test}" ] 
    then
    	[ "${inet}" = "inet" ] && print \
        	"addif "${test}" netmask + broadcast + deprecated -failover up" >>"${dest_file}"
    else 
        ${GREP} -w "netmask + broadcast -failover up" "${dest_file}" >/dev/null 2>&1
        [ ${?} -ne 0 ] && [ "${inet}" = "inet" ] && print \
		"netmask + broadcast -failover up" >>"${dest_file}"
    fi
	
    ${GREP} -w "plumb" "${dest_file}" >/dev/null 2>&1 
    [ ${?} -ne 0 ] && [ "${inet}" = "inet6" ] && print \
	"plumb" >>"${dest_file}"
    ${GREP} -w "up" "${dest_file}" >/dev/null 2>&1
    [ ${?} -ne 0 ] && [ "${inet}" = "inet6" ] && print \
	"up" >>"${dest_file}"
    # 1st character is a space otherwise -standby will make print fails
    [ "${inet}" = "inet" ] && print " ${standby}" >>"${dest_file}"

    replace_hostname_file
    print ${file}
    return ${SUCCESS}
}

#
# edit_group_in_hostname change the test address and standby flag settings of
# the adapter's IPMP group.
#
edit_group_in_hostname()
{
    typeset one
    typeset two
    typeset three

    get_hostname_file
    style_hostname_file | standby_hostname_file | ifparse_hostname_file | \
    while read one two three
    do
        case "${one}" in
                "addif")
		    # If a test address is provided, then change the old test address
		    # else remove the addif line all together
		    if [ -n "${test}" ]
		    then
                    	# IP address comes from previous call to chg_test_address
                    	if [ "${two}" = "${test_addr}" ]
                    	then
                        	# Must really be a test address
				print "${three}" | \
                        	${GREP} "deprecated" | ${GREP} -q "\-failover"
                        	[ ${?} -eq 0 ] && \
                            	print "${one} ${test} ${three}" >>"${dest_file}"
                    	# This is not the logical address we want to change
                    	else
                        	print ${one} ${two} ${three} >>"${dest_file}"
                    	fi
		    fi
                    ;;
                *)
                    # Don't use print as 1st character might be a -
                    echo ${one} ${two} ${three} >>"${dest_file}"
                    ;;
        esac
    done

    # Append addif as there is not found before
    # Append addif if a testaddress has been provided
    if [ -n "${test}" ] 
    then
	if [ "${inet}" = "inet" ] 
	then
    		${GREP} "addif ${test}" "${dest_file}" | ${GREP} "deprecated" | \
		${GREP} -q "\-failover"
		[ ${?} -ne 0 ] && print \
            		"addif ${test} netmask + broadcast + deprecated -failover up" >>"${dest_file}"
	fi
    else 
	# Test address has not been provided
	if [ "${inet}" = "inet" ] 
	then
		${GREP} -w "netmask + broadcast -failover up" "${dest_file}" >/dev/null 2>&1
		[ ${?} -ne 0 ] && [ "${inet}" = "inet" ] && print \
			"netmask + broadcast -failover up" >>"${dest_file}"
	fi
    fi
    # 1st character is a space otherwise -standby will make print fails
    print " ${standby}" >>"${dest_file}"

    replace_hostname_file
    print ${file}
    return ${SUCCESS}
}

#
# del_group_from_hostname delete from a given interface's hostname file the
# group and test address and standby settings.
#
del_group_from_hostname()
{
    typeset one
    typeset two
    typeset three

    get_hostname_file
    style_hostname_file | standby_hostname_file | ifparse_hostname_file | \
    while read one two three
    do
	case "${one}" in
		"group")
                    # Remove group
                    [ -n "${three}" ] && print "${three}" >>"${dest_file}"
                    ;;
                "addif")
		    # Remove test address
                    print "${two} ${three}" | ${GREP} -q "deprecated -failover"
                    [ ${?} -ne 0 ] && \
                        print ${one} ${two} ${three} >>"${dest_file}"
                    ;;
                *)
                    # Don't use print as 1st character might be a -
                    echo ${one} ${two} ${three} >>"${dest_file}"
                    ;;
	esac
    done

    # If hostname file is not empty, reset standby
    # 1st character is a space otherwise -standby will make print fails
    [ -s "${dest_file}" ] && print " -standby" >>"${dest_file}"

    replace_hostname_file
    print ${file}
    return ${SUCCESS}
}

#
# grp_group_in_hostname change the group setting in a given interface's
# hostname file.
# IPv6 supported.
#
grp_group_in_hostname()
{
    typeset one
    typeset two
    typeset three

    get_hostname_file
    ifparse_hostname_file <"${file}" | while read one two three
    do
        case "${one}" in
                "group")
                    # Empty group name means no group
                    if [ -z "${group}" ]
                    then
                        [ -n "${three}" ] && print "${three}" >>"${dest_file}"
                    else
                        print "group ${group} ${three}" >>"${dest_file}"
                    fi
                    ;;
                *)
                    # Don't use print as 1st character might be a -
                    echo ${one} ${two} ${three} >>"${dest_file}"
                    ;;
        esac
    done
    replace_hostname_file
    print ${file}
    return ${SUCCESS}
}

#
# Entry point
#
[ ${#} -lt 1 ] && end ${FAILURE} "add | edit | del | grp"

# Cleanup even if interrupted
trap "${RM} -f ${dest_file} 2>/dev/null; exit 1" 1 2 3 15

case "${1}" in
    "add")
        [ ${#} -ne 6 ] && \
            end ${FAILURE} "add group adapter inet test standby"
	group="${2}"
	adapter="${3}"
	inet="${4}"
	test="${5}"
	standby="${6}"
        add_group_to_hostname
        ;;
    "edit")
        [ ${#} -ne 6 ] && \
        end ${FAILURE} "edit adapter inet test standby persistent"
	adapter="${2}"
	inet="${3}"
	test="${4}"
	standby="${5}"
        persistent=${6}
	[ "${inet}" = "inet" ] && chg_test_address
        set_standby
        # chg_test_address must have been called before edit_group_in_hostname
        [ ${persistent} -eq 1 ] && edit_group_in_hostname
        ;;
    "del")
        [ ${#} -ne 4 ] && \
            end ${FAILURE} "del adapter inet persistent"
	adapter="${2}"
	inet="${3}"
	standby="-standby"
        persistent=${4}
        # Order of the 3 calls hereafter matter
	[ "${inet}" = "inet" ] && del_test_address
        del_adapter
	[ "${inet}" = "inet" ] && set_standby
        [ ${persistent} -eq 1 ] && del_group_from_hostname
        ;;
    "grp")
        [ ${#} -ne 4 ] && end ${FAILURE} "grp group adapter inet"
	group="${2}"
	adapter="${3}"
	inet="${4}"
        grp_group_in_hostname
        ;;
    *)
        end ${FAILURE}  "${1}"
        ;;
esac
end 0
