#!/bin/sh
# $Copyright$
# Copyright (C) Fujitsu Technology Solutions 2009, 2010
# All rights reserved
#
# $Id: oemups.sh,v 1.9 2007-03-02 12:53:02+01 richter Exp richter $
#
# NAME
#	oemups - Maintain configuration of Uninterruptible Power Supplies
#
# SYNOPSIS
#	oemups [-a][-d n][-l][-hV]
#
# DESCRIPTION
# 	This script configures the Uninterruptible Power Supplies (UPS)
#	by assigning UPS specific information (vendor, model, IP address,
#	OID) and by building associations between system cabinets and UPSs.
#
#    In the config space there is
#    + an array cabinetId[i], i = 0...n,  where i = 0 is for the local cabinet
#      and the order of the others is as returnedby CM_DetectedSECabinets
#    + an array UPSentry[j], j = 0...m, whose elements hold properties of one
#      configured UPS. Usually, m <= n < 255.
#    + an array UPSindex[id], id = 0...n, indexed by the elements from cabinetId[]
#      whose elements hold the index of the UPS in UPSentry[j] for the cabinet
#      associated with cabinetId[i] or -1.
#      UPSindex[<cabinetId>] = <UPS index into UPSentry[] associated with cabinetId>
#
#          UPSentry[]              UPSindex[]                cabinetId list
#       +-------------+          +----------+                +-------------+
#       | UPS entry 1 | 0 <------+---o      | cabinetId 1    | cabinetId 1 | 0
#       +-------------+          +----------+                +-------------+
#       | UPS entry 2 | 1 <------+---o      | cabinetId 2    | cabinetId 2 | 1
#       +-------------+          +----------+                +-------------+
#             ...                      ...                         ...
#
# OPTIONS
#	If called without options, the script displays ...
#	Otherwise, option
#	a		adds a new UPS entry to the system
#	d n		deletes an UPS entry with number n
#	l		lists all configured UPSs
#	h		show this usage message
#	V		show verbose progress messages
#
# SEE ALSO
#	eecdcp(8)
#	ServerControl Command Interface (SCCI) Specification
#
# EXIT STATUS
#	0 OK
#	1 Runtime Error
#	2 Argument Error
#
# CHANGE HISTORY
#	Mar 2007, -rit
#		Introduced CSV_ConfUpsIndex to support OID cabinetUpsUsed.
#	Nov 2006, -rit
#		Initial implementation as shell script

# ----------------------------------------------------------------------
# Constants
# ----------------------------------------------------------------------

MYSELF="${0##*/}"
MYLOGFILE=${MYLOGFILE:="/var/log/srvmagt/log.oemups"}
HASH="####################"

CM_IdentificationCabinetNumber=e204
CM_DetectedSECabinets=0220

CM_RCSV=e001		# CM_ReadConfigurationSpace
CM_WCSV=e002		# CM_WriteConfigurationSpace

CSV_ConfNumberUps=0100
CSV_ConfUpsIndex=0101
CSV_ConfUpsMaxDischargeTime=0102
CSV_ConfUpsAgentOid=0103
CSV_ConfUpsAgentIpAddress=0104
CSV_ConfUpsVendor=0105
CSV_ConfUpsModel=0106
CSV_ConfUpsCabinets=0107
CSV_ConfNumberBbu=0110

CSVV_NoUps=ff

# Known, predefined UPSs

UPS_Vendor[1]="\"American Power Conversion\""
UPS_Vendor[2]="\"SIEMENS AG\""
UPS_Vendor[3]="\"Fuji Electric\""
UPS_Vendor[4]="\"MasterGUARD\""
 UPS_Model[1]="\"APC UPS\""
 UPS_Model[2]="\"Power UPS 7\""
 UPS_Model[3]="\"Fuji UPS"
 UPS_Model[4]="\"MasterGUARD UPS"
UPS_AgtOID[1]="\"1.3.6.1.4.1.318.2\""
UPS_AgtOID[2]="\"1.3.6.1.2.1.33\""
UPS_AgtOID[3]="\"1.3.6.1.4.1.1356\""
UPS_AgtOID[4]="\"1.3.6.1.2.1.33\""

# Internationalized messages overloaded later according to LANG "de_DE.UTF-8"

BANNER="
ServerView Linux Agents OEM UPS Maintenance
(c) Fujitsu Technology Solutions 2009"

USAGE="#### Usage:
    $MYSELF [-a][-d n][-l][-h]
    a       adds a new UPS entry to the system
    d n     deletes an UPS entry with number n
    l       lists all configured UPSs
    h       show this usage message\n"

CabIndexDuplicate="Duplicate cabinet index %d!\n"
CabIndexIsNotInteger="Cabinet index %s is not an integer 1 ... 99!\n"
CabIndexOutOfRange="Invalid cabinet index %s!\n"
CabinetsOnUPS="Connected to cabinets:"
CallingEecdcp="eecdcp: called with %s\n"
Cancel="%s\n[C=Cancel]> "
CannotDecrementUPSnumber="Cannot decrement the UPS number, UPS #%d duplicates UPS #%d!\n"
CannotReadCabinetList="Cannot get the cabinet list from local enclosure controller!\n"
CannotReadUPSindex="Cannot read the UPS index for cabinet ID %d!\n"
CannotReadUPSinfo="Cannot read info of UPS %d!\n"
CannotReadUPSnumber="Cannot read UPS number!\n"
CannotWriteUPSindex="Cannot write the UPS index for cabinet ID %d!\n"
CannotWriteUPSinfo="Cannot write info of UPS %d!\n"
ConfigureComplete="Configuring UPS %d %s with status %d (%s %s).\n"
Configuring="Configuring UPS for ServerView ...\n"
Deleting="Deleting UPS '%s' ...\n"
DeletingComplete="Deleting UPS '%s' %s with status %d (%s %s).\n"
DialogReturnsCabs="Dialog returned IDs of cabinets connected to this UPS [%s]\n"
DialogConfirmConf="Configure the UPS with these value?"
DialogEnterIPaddr="Please enter the UPS's IP address (n1.n2.n3.n4, ni = 0...255):"
DialogReturnsIPaddr="Dialog returned IP address '%s'.\n"
DialogSelectCabs="Please select one or more cabinets to connect to this UPS (n1 n2 ...):"
DialogSelectUPS="Please select an UPS:"
DialogUPS="Dialog returned predefined UPS %d, '%s'.\n"
IPaddrIsInvalid="IP address %s is not in format n1.n2.n3.n4, ni = 0...255!\n"
LeavingEecdcp="eecdcp: status %d, output '%s'\n"
Listing="Listing configured UPSs:\n"
ListingComplete="Listing UPSs %s with status %d (%s %s).\n"
LocalCabAndCabList="The local cabinet is '%s', the cabinet ID list is [%s].\n"
LocalCabToUPSMap="The cabinet:UPS association is [%s].\n"
MustBeBash="You must have the bash to execute this script. Exit!\n"
MustBeRoot="%s: You must be root to execute this program. Exit!\n"
NoUPSconfigured="There are no UPSs configured.\n"
OKorCancel="%s\n[<RETURN>=OK C=Cancel]> "
UPSnumberNotInRange="The indicated UPS '%s' is not in range 1 ... %d!\n"

UPS="UPS"
UPSvendor="UPS Vendor:"
UPSmodel="UPS Model:"
IPaddr="IP Address:"
SNMPoid="SNMP OID:"
BBUavailable="BBU Available:"
Cabinet="cabinet"

Yes="yes"
No="no"
Aborted="aborted"
Completed="completed"
Failed="failed"

Ok="OK status"
Error="Runtime error"
ArgumentError="Argument error"
Undefined="Undefined status"

EmptyMessage="Empty message"
InvalidAction="Invalid option"
InvalidArgument="Invalid argument"
InvalidOpcode="Invalid opcode"
InvalidOpcodeExt="Invalid opcode extension"

# ----------------------------------------------------------------------
# General Transformations
# ----------------------------------------------------------------------

# mapStatus - map decimal exit status value to stdout
#
# $1		decimal status value
# stdout 	Four separate words go to stdout:
#			{$Completed|$Aborted} decimalStatus statusWord1 statusWord2

function mapStatus() {
	case $1 in
	0) echo $Completed $1 $Ok ;;			# success
	1) echo $Aborted   $1 $Error ;;			# generic or unspecified error
	2) echo $Aborted   $1 $ArgumentError ;;	# invalid or excess argument(s)
	*) echo $Aborted   $1 $Undefined ;;
	esac
}

# mapYesNo - map 0|1 to yes|no
#
# $1	0|00|1|01

function mapYesNo() {
	case $1 in
	0|00)	echo "$No" ;;
	1|01)	echo "$Yes" ;;
	*)		errorExit $FUNCNAME "$InvalidArgument" "$1" ;;
	esac
}

# makeMSBfromXpairs - transform 2 hexdigit pairs into a 16-bit MSB number
#
# $1 ... 	each argument is a hexdigit pair,
#			two adjacent pairs present a (16-bit) hexnumber in LSB.
# stdout	Present the arguments as (16-bit) hexnumbers in MSB.

function makeMSBfromXpairs() {
	local s=""

	[ "$#" -gt 0 ] || return

	while [ $# -gt 1 ] ; do
		s="$s $2$1"
		shift 2
	done
	printf "${s#* }"	# remove the leading space
}

# makeXfromMSB - transform each 16-bit MSB numbers into 2 hexdigit pairs in LSB
#
# $@ 		each argument is a 16-bit MSB numbers,
# stdout	Present the arguments as space-separated hexdigit pairs,
#			two adjacent pairs present the MSB number in LSB.

function makeXpairsfromMSB() {
	local s=""

	[ "$#" -gt 0 ] || return
	while [ $# -gt 0 ] ; do
		s="$s ${1:2:2} ${1:0:2}"
		shift
	done
	printf "${s#* }"	# remove the leading space
}

# validateCabinets - validate a selected subset of indices
#
# $1		predefined list of valid cabinet IDs (IFS-separated words)
# $2		a set of 1-based indices into this list to validate
#			(index exists in list, no duplicate indices)
# Returns	0|1 if OK or not

function validateCabinets() {
	local cabList idx idx2 validIndices="" result=0

	[ -z "$1" -o -z "$2" ] && return 1

	cabList=($1)	# put into array starting w/ index 0
	validIndices=""

	for idx in $2; do
		# NOTE: We restrict valid cabinet indices here to 0 ... 99
		#		to easily test that an input value is a number.
		#		100 cabinets appear to be enough for some time to come.

		case $idx in
		[0-9]|[0-9][0-9])
			[ $idx -gt 0 -a $idx -le ${#cabList[@]} ] || {
				printf "$CabIndexOutOfRange" $idx
				result=1
			}
			for idx2 in $validIndices; do
				[ $idx2 = $idx ] && {
					printf "$CabIndexDuplicate" $idx
					result=1
				}
			done
			validIndices="$validIndices $idx" ;;

		*)
			printf "$CabIndexIsNotInteger" $idx
			result=1 ;;
		esac
	done
	return $result
}

# ----------------------------------------------------------------------
# Monolog: (Unconfirmed) output with logging
# ----------------------------------------------------------------------

# errorExit - exit the script because of a logical error
#
# $1		function involved (or "main")
# $2 + $3	error cause and parameter
#
# printf  - display a FORMATTED message (this is the bash built-in cmd)
# display - display and log a simple FORMATTED message
# verbose - display and log a verbose FORMATTED message (see -V option)
# logonly - log a verbose FORMATTED message (does not clutter stdout!)
#
# $@	one or more arguments containing the messages usually
#		as in printf format [arguments]
#
# showMsg - output a FORMATTED message to various destinations
#
# $1		"ALWAYS" or "VERBOSE": mode in which to display this message
# $2ff		format [arguments] (see printf)
#
# stdout 	the formatted message (copied also to MYLOGFILE)

function errorExit() {
	showMsg ALWAYS "SCRIPT ERROR in %s: %s '%s'! Exit.\n" "$1" "$2" "$3"
	exit 1
}

function display() {
	showMsg ALWAYS "$@"		# MUST retain individual arguments!
}

function verbose() {
	showMsg VERBOSE "$@"	# MUST retain individual arguments!
}

function logonly() {
	showMsg LOGONLY "$@"	# MUST retain individual arguments!
}

function showMsg() {
	[ $# -eq 0 ] && errorExit $FUNCNAME "$EmptyMessage" ""

	case $1 in
	ALWAYS)		# display message to stdout and to log file
		shift
		printf "$(date +'%T') $@" >>$MYLOGFILE
		printf "$@" ;;

	VERBOSE)	# display message to log file and to stdout if -v option
		shift
		printf "$(date +'%T') $@" >>$MYLOGFILE
		[ $Voption -eq 1 ] && printf "$@" ;;

	LOGONLY)	# display message only to log file  (does not clutter stdout!)
		shift
		printf "$(date +'%T') $@" >>$MYLOGFILE ;;

	*) errorExit $FUNCNAME "$InvalidArgument" "$1" ;;
	esac
}

# ----------------------------------------------------------------------
# Dialog: Output with confirmation and answers
# ----------------------------------------------------------------------

# confirmDialog - dialog to confirm a question
#
# $1		question to confirm
# Returns	0|1 if OK|Cancel

function confirmDialog() {
	local answer

    while true; do
        printf "$OKorCancel" "$1"
        read answer

		[ -z "$answer" ] && return 0
		[ "$answer" = "c" -o "$answer" = "C" ] && return 1
    done
}

# askDialog - dialog with string as response
#
# $1		prompt string
# stdout	prompt
# Output	user's response (on return 0) into GLOBAL variable AnswerOfDialog
# Returns	0|1 if OK|Cancel

AnswerOfDialog=""
function askDialog() {
	local answer

    while true; do
        printf "$Cancel" "$1"
        read answer

		[ -z "$answer" ] && continue
        case "$answer" in
			[cC])	return 1 ;;
			*)      AnswerOfDialog="$answer" ; return 0 ;;
        esac
    done
}

# fourWayDialog - dialog with one of 4 possible answers
#
# $1 - $5	prompt string plus four possible answers
# stdout	prompt ans possible answers
# Returns	the number 1 thru 4 of the chosen answer

function fourWayDialog() {
	local answer

    while true; do
        printf "%s\n\t%s\n\t%s\n\t%s\n\t%s\n> " "$1" "$2" "$3" "$4" "$5"
        read answer

        case "$answer" in
			1|2|3|4) return $answer ;;
			*)      continue ;;
        esac
    done
}

# ----------------------------------------------------------------------
# IP Address
# ----------------------------------------------------------------------

# makeDottedIPaddr - transform $1 into familiar dotted IP address
#
# $1		a string of four IFS-separated hexdigit pairs
# stdout	result to stdout

function makeDottedIPaddr() {
	local a b c d

	[ -z "$1" ] && {
		printf ""
		return
	}
	set $1
	let a=16#$1 b=16#$2 c=16#$3 d=16#$4
	printf "$a.$b.$c.$d"
}

# validateIPaddr - validate an IP address in dotted decimal notation
#                  (four dot-separated numbers 0 thru 255)
#
# $1		the string to validate
# Returns	0|1 if OK or not OK.

function validateIPaddr() {
	local ipaddr i invalid=1

	[ -z "$1" ] && return 1

	ip=(${1//./ })	# replace '.' by ' ' and put fields into ip[]
	[ ${#ip[@]} -eq 4 ] && {
		invalid=0
		for i in 0 1 2 3; do
			case ${ip[$i]} in
			[0-9]|[0-9][0-9]|[0-2][0-9][0-9])
				[ ${ip[$i]} -gt 255 ] && invalid=1 ;;
			*)	invalid=1 ;;
			esac
		done
	}
	[ $invalid -eq 1 ] && printf "$IPaddrIsInvalid" "$1"
	return $invalid
}

# ----------------------------------------------------------------------
# eecd Access
# ----------------------------------------------------------------------

# eecdcpCmd - call eecdcp(8) with given arguments
#
# $1ff		additional arguments for eecdcp(8)
# output	command response (if any)
# Returns	0|1 if OK or not OK

EecdOutput=""

function eecdcpCmd() {
	local result output

	logonly "$CallingEecdcp" "$*"

	if [ $Toption -eq 0 ]; then
		EecdOutput=$(eecdcp -c "$@" 2>/dev/null)
		printf "$EecdOutput"

	else
		myEecdcp -c "$@"
	fi

	result=$?
	logonly "$LeavingEecdcp" "$?" "$EecdOutput"
	return $result
}

# ----------------------------------------------------------------------
# UPS
# ----------------------------------------------------------------------

# <TEST>
# Simulate 3 already configured UPSs for the list and delete operation
# plus 1 unconfigured UPS for the add operation.
# Also simulate 1 server cabinet with MSB Id "0000" (0) and two SE
# cabinets with IDs "0064" (100), "0100" (256).
#
# NOTE:
#	The bash does not modify global data within a function called via $()!

MyUPSs="03"					# ConfNumberUPS			BYTE

MyUPS0[1]="00"				# ConfUpsIndex
MyUPS0[2]="-1"				# ConfUpsMaxDischargeTime (obsolete)
MyUPS0[3]="\"1.3.6.1.2.1.33\""	# ConfUpsAgentOid	STRING
MyUPS0[4]="\"192.168.2.2\"" # ConfUpsAgentIpAddress	STRING
MyUPS0[5]="\"SIEMENS AG\""	# ConfUpsVendor			STRING
MyUPS0[6]="\"Power UPS 7\"" # ConfUpsModel			STRING
MyUPS0[7]="00 00 00 01"		# ConfUpsCabinets		WORD array
MyUPS0[10]="0"				# ConfNumberBbu			BYTE

MyUPS1[1]="01"
MyUPS1[2]="-1"
MyUPS1[3]="\"1.3.6.1.4.1.318.2\""
MyUPS1[4]="\"172.25.89.225\""
MyUPS1[5]="\"American Power Conversion\""
MyUPS1[6]="\"APC UPS\""
MyUPS1[7]="64 00 00 00"
MyUPS1[10]="1"

MyUPS2[1]="02"
MyUPS2[2]="-1"
MyUPS2[3]="\"1.3.6.1.4.1.1356\""
MyUPS2[4]="\"166.0.1.225\""
MyUPS2[5]="\"Fuji Electric\""
MyUPS2[6]="\"Fuji UPS\""
MyUPS2[7]="00 01 64 00"
MyUPS2[10]="1"

MyUPSindex[0]="00"			# for cabId 00 00
MyUPSindex[1]="01"			# for cabId 64 00
MyUPSindex[2]="02"			# for cabId 00 01

# myEecdcp - the eecdcp command for offline test witjout eecdcp present
#
# $@		individual eecdcp arguments, e.g.
#       	oc=e001 oe=0107 cab=0000 oi=0 value
# Returns 	0.

function myEecdcp() {
	case $1 in
	-c)	case $2 in
		oc=e204) printf "00 00"				# CM_IdentificationCabinetNumber
				EecdOutput="00 00" ;;
		oc=0220) printf "64 00 00 01"		# CM_DetectedSECabinets
				EecdOutput="01 00 02 00" ;;
		oc=e001)							# CM_ReadConfigurationSpace
			case $3 in
			oe=0100)
				printf "$MyUPSs"
				EecdOutput="$MyUPSs"
				return 0 ;;

			oe=0101)
				case $5 in
				oi=0000)   printf "${MyUPSindex[0]}" ; EecdOutput="${MyUPSindex[0]}" ;;
				oi=0064)  printf "${MyUPSindex[1]}" ; EecdOutput="${MyUPSindex[1]}" ;;
				oi=0100) printf "${MyUPSindex[2]}" ; EecdOutput="${MyUPSindex[2]}" ;;
				*)	return 1 ;;
				esac ;;

			oe=0102|oe=0103|oe=0104|oe=0105|oe=0106|oe=0107|oe=0110)
				case $5 in
				oi=0) printf "${MyUPS0[${3#oe=01}]}" ; EecdOutput="${MyUPS0[${3#oe=01}]}" ;;
				oi=1) printf "${MyUPS1[${3#oe=01}]}" ; EecdOutput="${MyUPS1[${3#oe=01}]}" ;;
				oi=2) printf "${MyUPS2[${3#oe=01}]}" ; EecdOutput="${MyUPS2[${3#oe=01}]}" ;;
				oi=3) printf "${MyUPS3[${3#oe=01}]}" ; EecdOutput="${MyUPS3[${3#oe=01}]}" ;;
				*)	return 1 ;;
				esac ;;

			*)	errorExit $FUNCNAME "$InvalidOpcodeExt" "$3" ;;
			esac ;;

		oc=e002)							# CM_WriteConfigurationSpace
			case $3 in
			oe=0100)
				MyUPSs=$(printf "%02d" $6)
				printf "$MyUPSs"
				EecdOutput=$MyUPSs
				return 0 ;;

			oe=0101|oe=0102|oe=0103|oe=0104|oe=0105|oe=0106|oe=0107|oe=0110)
				case $5 in
				oi=0) MyUPS0[${3#oe=01}]="$6" ; printf "${MyUPS0[${3#oe=01}]}" ; EecdOutput="${MyUPS0[${3#oe=01}]}";;
				oi=1) MyUPS1[${3#oe=01}]="$6" ; printf "${MyUPS1[${3#oe=01}]}" ; EecdOutput="${MyUPS1[${3#oe=01}]}";;
				oi=2) MyUPS2[${3#oe=01}]="$6" ; printf "${MyUPS2[${3#oe=01}]}" ; EecdOutput="${MyUPS2[${3#oe=01}]}";;
				oi=3) MyUPS3[${3#oe=01}]="$6" ; printf "${MyUPS3[${3#oe=01}]}" ; EecdOutput="${MyUPS3[${3#oe=01}]}";;
				*)	return 1 ;;
				esac ;;

			*)	errorExit $FUNCNAME "$InvalidOpcodeExt" "$3" ;;
			esac ;;

		*)	errorExit $FUNCNAME "$InvalidOpcode" "$2" ;;
		esac
		;;

	*)	errorExit $FUNCNAME "$InvalidAction" "$1" ;;
	esac
	return 0
}
# </TEST>

# getNumberOfUPS - read the number of already configured UPSs
#
# Returns	the number of UPSs

function getNumberOfUPS() {
	local upsNo=0

	upsNo=$(eecdcpCmd oc=$CM_RCSV oe=$CSV_ConfNumberUps cab=$ThisCabinet oi=0) && [ -n "$upsNo" ] || {
		logonly "$CannotReadUPSnumber"
		return 0
	}
	[ $upsNo -eq 0 ] && display "$NoUPSconfigured"
	return $upsNo
}

# configureUPS - configure an UPSs
#
# Returns	0|1 if OK or not OK

function configureUPS() {
	local upsNo newUPS answer question i=0 cab void

	getNumberOfUPS
	upsNo=$?

	# Ask new UPS's data

	fourWayDialog "$DialogSelectUPS" \
			"1 ${UPS_Vendor[1]}" "2 ${UPS_Vendor[2]}" "3 ${UPS_Vendor[3]}" "4 ${UPS_Vendor[4]}" "4 Cancel"
	answer=$?
	case $answer in
		1|2|3)
			newUPS[3]="${UPS_AgtOID[$answer]}"
			newUPS[5]="${UPS_Vendor[$answer]}"
			newUPS[6]="${UPS_Model[$answer]}"
			newUPS[10]=0
			;;
		4)	return 1 ;;
	esac
    verbose "$DialogUPS" $answer "${newUPS[5]}"

	while true; do
		askDialog "$DialogEnterIPaddr" || return 1
		validateIPaddr $AnswerOfDialog || continue
		newUPS[4]="\"$AnswerOfDialog\""
		break
	done
    verbose "$DialogReturnsIPaddr" "${newUPS[4]}"

	question="$DialogSelectCabs"
	for cab in $CabinetList; do
		if [ $((i++ % 4)) -eq 0 ]; then
			question="$question$(printf '\n\t%2d %s %s ' $i $Cabinet $cab)"
		else
			question="$question$(printf '%2d %s %s ' $i $Cabinet $cab)"
		fi
	done

	while true; do
		askDialog "$question" || return 1
		validateCabinets "$CabinetList" "$AnswerOfDialog "|| continue
		newUPS[7]=""
		cabArray=($CabinetList)
		for i in $AnswerOfDialog; do
			newUPS[7]="${newUPS[7]} ${cabArray[$((i - 1))]}"
		done
		newUPS[7]=${newUPS[7]# *}
		break
	done
	verbose "$DialogReturnsCabs" "${newUPS[7]}"

	# Confirm new USP data, then establish the new UPS

	question=$(printf "%s\n\t%-24s%s\n\t%-24s%s\n\t%-24s%s\n\t%-24s%s\n\t%-24s[%s]\n" \
		"$DialogConfirmConf" \
		"$UPSvendor"	"${newUPS[5]}" \
		"$UPSmodel"		"${newUPS[6]}" \
		"$IPaddr"		"${newUPS[4]}" \
		"$SNMPoid"		"${newUPS[3]}" \
		"$CabinetsOnUPS"	"${newUPS[7]}")
	confirmDialog "$question" || return 1

	eecdcpCmd oc=$CM_WCSV oe=$CSV_ConfUpsVendor cab=$ThisCabinet oi=$upsNo "${newUPS[5]}" >/dev/null &&
		eecdcpCmd oc=$CM_WCSV oe=$CSV_ConfUpsModel cab=$ThisCabinet oi=$upsNo "${newUPS[6]}" >/dev/null &&
		eecdcpCmd oc=$CM_WCSV oe=$CSV_ConfUpsAgentIpAddress cab=$ThisCabinet oi=$upsNo "${newUPS[4]}" >/dev/null &&
		eecdcpCmd oc=$CM_WCSV oe=$CSV_ConfUpsAgentOid cab=$ThisCabinet oi=$upsNo "${newUPS[3]}" >/dev/null &&
		eecdcpCmd oc=$CM_WCSV oe=$CSV_ConfUpsCabinets cab=$ThisCabinet oi=$upsNo "$(makeXpairsfromMSB ${newUPS[7]})" >/dev/null &&
		eecdcpCmd oc=$CM_WCSV oe=$CSV_ConfNumberBbu cab=$ThisCabinet oi=$upsNo "${newUPS[10]}" >/dev/null || {

		verbose "$CannotWriteUPSinfo" $upsNo
		return 1
	}

	void=$(eecdcpCmd oc=$CM_WCSV oe=$CSV_ConfNumberUps cab=$ThisCabinet oi=0 $((upsNo + 1)))
	UpsID=$((upsNo + 1))

	# The new UPS contains now a list of IDs of cabinets it is attached to.
	# Update the cabinet ID to UPS index mapping for these cabinets.

	for cab in ${newUPS[7]}; do
		eecdcpCmd oc=$CM_WCSV oe=$CSV_ConfUpsIndex cab=$ThisCabinet oi=$cab $upsNo >/dev/null || {
			verbose "$CannotWriteUPSindex" $upsNo
			return 1
		}
	done

	[ $Toption -eq 1 ] && listUPS # <TEST/>
	return 0
}

# listUPS - list the given UPSs with its attributes
#
# $1		1-base number within the configured UPSs
# Returns	0

function listUPS() {
	local upsNo oi s1 s2 s3 s4 s5 s6

	getNumberOfUPS
	upsNo=$?
	[ $upsNo -eq 0 ] && return 0

	display "$Listing"
	oi=0
	while [ $oi -lt $upsNo ]; do

		s1=$(eecdcpCmd oc=$CM_RCSV oe=$CSV_ConfUpsVendor cab=$ThisCabinet oi=$oi) &&
			s2=$(eecdcpCmd oc=$CM_RCSV oe=$CSV_ConfUpsModel cab=$ThisCabinet oi=$oi) &&
			s3=$(eecdcpCmd oc=$CM_RCSV oe=$CSV_ConfUpsAgentIpAddress cab=$ThisCabinet oi=$oi) &&
			s4=$(eecdcpCmd oc=$CM_RCSV oe=$CSV_ConfUpsAgentOid cab=$ThisCabinet oi=$oi) &&
			s5=$(eecdcpCmd oc=$CM_RCSV oe=$CSV_ConfUpsCabinets cab=$ThisCabinet oi=$oi)
			s6=$(eecdcpCmd oc=$CM_RCSV oe=$CSV_ConfNumberBbu cab=$ThisCabinet oi=$oi) || {

			verbose "$CannotReadUPSinfo" $((oi + 1))
			return 1
		}

		display "%s #%s\n\t%-24s%s\n\t%-24s%s\n\t%-24s%s\n\t%-24s%s\n\t%-24s%s\n\t%-24s[%s]\n" \
			"$UPS"			"$((oi + 1))" \
			"$UPSvendor"	"$s1" \
			"$UPSmodel"		"$s2"\
			"$IPaddr"		"$s3" \
			"$SNMPoid"		"$s4" \
			"$BBUavailable" "$(mapYesNo $s6)" \
			"$CabinetsOnUPS"	"$(makeMSBfromXpairs $s5)"

		let oi++
	done

	return 0
}

# deleteUPS - delete (unconfigure) an UPS
#
# $1		1-based number of the UPS to delete
# Returns	0|1 if OK or not OK

function deleteUPS() {
	local upsNo oi s1 s2 s3 s4 s5 s6 i

	getNumberOfUPS && return 0
	upsNo=$?

	[ -z "$1" ] || [ $1 -le 0 -o $1 -gt $upsNo ] && {
		display "$UPSnumberNotInRange" "$1" $upsNo
		return 1
	}

	# Remove this UPS from the cabinet ID to UPS index mapping

	i=0
	upsArray=($CabinetToUPS)
	for cab in $CabinetList; do
		[ ${upsArray[$i]} -eq $(($1 - 1)) ] && {
			eecdcpCmd oc=$CM_WCSV oe=$CSV_ConfUpsIndex cab=$ThisCabinet oi=$cab $CSVV_NoUps > /dev/null || {
				display "$CannotWriteUPSindex" $cab
				exit 1
			}
		}
		let i++
	done

	# Move the UPS with the highest number (unless it is itself deleted) into the evacuated space

	[ $1 -lt $upsNo ] && {
		oi=$((upsNo - 1))
		s1=$(eecdcpCmd oc=$CM_RCSV oe=$CSV_ConfUpsVendor cab=$ThisCabinet oi=$oi) &&
			s2=$(eecdcpCmd oc=$CM_RCSV oe=$CSV_ConfUpsModel cab=$ThisCabinet oi=$oi) &&
			s3=$(eecdcpCmd oc=$CM_RCSV oe=$CSV_ConfUpsAgentIpAddress cab=$ThisCabinet oi=$oi) &&
			s4=$(eecdcpCmd oc=$CM_RCSV oe=$CSV_ConfUpsAgentOid cab=$ThisCabinet oi=$oi) &&
			s5=$(eecdcpCmd oc=$CM_RCSV oe=$CSV_ConfNumberBbu cab=$ThisCabinet oi=$oi) &&
			s6=$(eecdcpCmd oc=$CM_RCSV oe=$CSV_ConfUpsCabinets cab=$ThisCabinet oi=$oi) || {

			verbose "$CannotReadUPSinfo" $upsNo
			return 1
		}

		oi=$(($1 - 1))
		eecdcpCmd oc=$CM_WCSV oe=$CSV_ConfUpsVendor cab=$ThisCabinet oi=$oi "$s1" >/dev/null &&
			eecdcpCmd oc=$CM_WCSV oe=$CSV_ConfUpsModel cab=$ThisCabinet oi=$oi "$s2" >/dev/null &&
			eecdcpCmd oc=$CM_WCSV oe=$CSV_ConfUpsAgentIpAddress cab=$ThisCabinet oi=$oi "$s3" >/dev/null &&
			eecdcpCmd oc=$CM_WCSV oe=$CSV_ConfUpsAgentOid cab=$ThisCabinet oi=$oi "$s4" >/dev/null &&
			eecdcpCmd oc=$CM_WCSV oe=$CSV_ConfNumberBbu cab=$ThisCabinet oi=$oi "$s5" >/dev/null &&
			eecdcpCmd oc=$CM_WCSV oe=$CSV_ConfUpsCabinets cab=$ThisCabinet oi=$oi "$s6" >/dev/null || {

			verbose "$CannotWriteUPSinfo" $1
			return 1
		}

		# Also remap all references to the former highest UPS index

		i=0
		for cab in $CabinetList; do
			[ ${upsArray[$i]} -eq $((upsNo - 1)) ] && {
				eecdcpCmd oc=$CM_WCSV oe=$CSV_ConfUpsIndex cab=$ThisCabinet oi=$cab $oi > /dev/null || {
					display "$CannotWriteUPSindex" $cab
					exit 1
				}
			}
			let i++
		done

	}

	# Clear the obsolete entry

	oi=$((upsNo - 1))
	eecdcpCmd oc=$CM_WCSV oe=$CSV_ConfUpsVendor cab=$ThisCabinet oi=$oi 0 >/dev/null &&
		eecdcpCmd oc=$CM_WCSV oe=$CSV_ConfUpsModel cab=$ThisCabinet oi=$oi 0 >/dev/null &&
		eecdcpCmd oc=$CM_WCSV oe=$CSV_ConfUpsAgentIpAddress cab=$ThisCabinet oi=$oi 0 >/dev/null &&
		eecdcpCmd oc=$CM_WCSV oe=$CSV_ConfUpsAgentOid cab=$ThisCabinet oi=$oi 0 >/dev/null &&
		eecdcpCmd oc=$CM_WCSV oe=$CSV_ConfNumberBbu cab=$ThisCabinet oi=$oi 0 >/dev/null &&
		eecdcpCmd oc=$CM_WCSV oe=$CSV_ConfUpsCabinets cab=$ThisCabinet oi=$oi 0 >/dev/null || {

		verbose "$CannotWriteUPSinfo" $upsNo
		return 1
	}

	# Adjust the UPS number

	eecdcpCmd oc=$CM_WCSV oe=$CSV_ConfNumberUps cab=$ThisCabinet oi=0 $oi >/dev/null || {
		verbose "$CannotDecrementUPSnumber" $1 $upsNo
		return 1
	}

	[ $Toption -eq 1 ] && listUPS # <TEST/>

	return 0
}

# ----------------------------------------------------------------------
# Main
# ----------------------------------------------------------------------

# Global variables (SHOULD begin with capital letter)

Toption=0
Voption=0
UpsID=-1
ThisCabinet=-1
CabinetList=""
CabinetToUPS=""

verbose "$(date +'%b %e %Y') %s %s LANG='%s' %s $*\n" $HOSTNAME $HASH $LANG $MYSELF

# Make sure that a decent shell and path is used and root privileges apply
# Source internationalized messages (e.g. for LANG="de_DE.UTF-8") if any

SRVMAGT=/etc/srvmagt
[ -f $SRVMAGT/messages/oemups_${LANG%_*}.msg ] && source $SRVMAGT/messages/oemups_${LANG%_*}.msg

PATH=/bin:/sbin:/usr/bin:/usr/sbin
[ -n "${BASH}" ] || {
	printf "$MustBeBash"
	exit 2
}

[ $(id -un) = "root" ] || {
	printf "$MustBeRoot" $MYSELF
	exit 2
}

action=list
result=0
while getopts ad:hlTV optChar; do
	case "$optChar" in
	a)	action=add ;;
	d)	action=delete ; UpsID=$OPTARG ;;
	l)	action=list ;;
	V)	Voption=1 ;;
	T)	Toption=1 ;;
	h|[?])
		printf "$BANNER\n\n"
		printf "$USAGE"
		[ "$1" = "-h" ] && exit 0
		exit 2
		;;
	esac
done
shift $(($OPTIND - 1))

printf "$BANNER\n\n"

# Get the local cabinet ID, the list of detected cabinets, and
# the list of cabinets to UPS association. Both lists are 1:1
# associated.

ThisCabinet=$(eecdcpCmd oc=$CM_IdentificationCabinetNumber) &&
		[ -n "$ThisCabinet" ] && ThisCabinet=$(makeMSBfromXpairs $ThisCabinet) &&
		list=$(eecdcpCmd oc=$CM_DetectedSECabinets cab=$ThisCabinet) || {
	display "$CannotReadCabinetList"
	exit 1
}

CabinetList=$ThisCabinet
[ -z "$list" ] || CabinetList="$ThisCabinet $(makeMSBfromXpairs $list)"
display "$LocalCabAndCabList" "$ThisCabinet" "$CabinetList"

for cab in $CabinetList; do
	ups=$(eecdcpCmd oc=$CM_RCSV oe=$CSV_ConfUpsIndex cab=$ThisCabinet oi=$cab) || {
		display "$CannotReadUPSindex" $cab
		exit 1
	}
	let ups=16#$ups
	[ $ups -eq 255 ] && ups=-1
	CabinetToUPS="$CabinetToUPS $ups"
done
logonly "$LocalCabToUPSMap" "$CabinetToUPS"

case $action in
	add)
   		verbose "$Configuring"
		configureUPS
		result=$?
		display "$ConfigureComplete" $UpsID $(mapStatus $result) ;;

	list)
		listUPS
		result=0
		display "$ListingComplete" $(mapStatus $result) ;;

	delete)
		display "$Deleting" $UpsID
		deleteUPS $UpsID
		result=$?
		display "$DeletingComplete" $UpsID $(mapStatus $result) ;;

	*)	errorExit "main" "$Invalid action" "$action" ;;
esac

# ----------------------------------------------------------------------
# Done
# ----------------------------------------------------------------------

exit $result

# EOF
