#!/bin/ksh93
#  ALTRAN_PROLOG_BEGIN_TAG
#  This is an automatically generated prolog.
#
#  Copyright (C) Altran ACT S.A.S. 2017,2018,2019,2020,2021,2022.  All rights reserved.
#
#  ALTRAN_PROLOG_END_TAG
#
# @(#)  5881272 43haes/usr/sbin/cluster/events/resource_common.sh, 61aha_r726, 2205A_aha726, May 16 2022 12:15 PM

#=============================================================================
#
# Name:        SerializeAsArray
#
# Description: This function is called when the query action is invoked in
#              such a way that the data set is simple, and is stored in an
#              associative array. By default, the output is simply one value
#              per line, with the values doublequoted.
#
#              Optionally, the user may request colon-delimited or XML-
#              formatted output. Also optionally, the user may specify one
#              or more specific attributes that they want to see, in which
#              case *only* those attributes are displayed. It should be noted
#              that the order in which the user specifies those attributes is
#              *not* honored.
#
# Inputs:      array   A reference to the pre-populated array that
#                      contains the data to be formatted and displayed.
#
#              class   The class, or type, of the data that was passed in,
#                      such as "cluster", "site", "node", etcetera.
#
#              CLMGR_DELIMITER  Optional. If set (to any value), the output
#                           will be delimited by the value in this variable.
#
#              CLMGR_COLON  Optional. If set to non-zero, the output will be
#                           colon-delimited.
#
#              CLMGR_XML    Optional. If set (to any value), the output will
#                           be formatted in XML.
#
# Outputs:     The data in the provided associative array is displayed in the
#              appropriate format.
#
# Returns:     0 if no problems are encountered.
#              1 if something goes wrong.
#
#=============================================================================
function SerializeAsArray {
    . $HALIBROOT/log_entry "$0()" "$CL" max

    typeset -n array=$1
    typeset -u class=$2

    if (( ${#array[*]} == 0 )); then
        log_return_msg "$RC_ERROR" "$0()" "$LINENO"
        return $?
    fi

    [[ -z $class ]] && class="ITEM"

    typeset -i COUNT=0 INDEX=0
    typeset element=

    if [[ $CLMGR_DELIMITER == *([[:space:]]) ]] && \
       (( CLMGR_COLON ))
    then
        CLMGR_DELIMITER=":"
    fi

    if (( CLMGR_JSON )); then
        : Since JSON output was requested, print our standard clmgr JSON header
        print "{\n    \"clmgr\": {"
    fi

    #
    : For XML and JSON, a class header must be displayed before displaying
    : any of the object names. That also requires some minor spelling
    : improvements to the class names to correctly indicate plurality.
    #
    if (( CLMGR_XML )); then
        if [[ $class != "CLUSTER" ]] && (( ! CLMGR_VERBOSE ))
        then
            case $class in
                *SS) print "<${class}ES>"    ;;
                 *Y) print "<${class%Y}IES>" ;;
                  *) print "<${class}S>"     ;;
            esac
        fi
    elif (( CLMGR_JSON )); then
        typeset -l class_lc=$class
        case $class_lc in
            *ss) print -n "    \"${class_lc}es\": [ "    ;;
             *y) print -n "    \"${class_lc%y}ies\": [ " ;;
              *) print -n "    \"${class_lc}s\": [ "     ;;
        esac
    fi

    #
    : Display all the object names for class $class
    #
    for (( INDEX=0; INDEX<${#array[*]}; INDEX++ ))
    do
        element=${array[$INDEX]}
        [[ -z $element ]] && continue

        if [[ $CLMGR_DELIMITER != *([[:space:]]) ]]; then
            (( COUNT )) && print -n "$CLMGR_DELIMITER"
            print -n -- $element
        elif (( CLMGR_XML )); then
            print "    <$class>$element</$class>"
        elif (( CLMGR_JSON )); then
            print -n "\"$element\""
            (( ( INDEX + 1 ) == ${#array[*]} )) && print " ]" || print -n ", "
        else
            print -- "$element"
        fi
        (( COUNT++ ))

        # Slow down the loop a fraction, to avoid spiking the CPU
        LANG=C sleep .001
    done

    if [[ $CLMGR_DELIMITER != *([[:space:]]) ]]; then
        print
    elif (( CLMGR_XML )); then
        #
        : For XML, a class closer must be displayed after displaying the object
        : names for the class. That also requires some minor spelling changes
        : to the class names to correctly indicate plurality.
        #
        case $class in
            *SS) print "</${class}ES>"    ;;
             *Y) print "</${class%Y}IES>" ;;
              *) print "</${class}S>"     ;;
        esac
    elif (( CLMGR_JSON )); then
        #
        : JSON also requires a "closer" to indicate that all the properties
        : that need to be displayed, actually _have_ been displayed.
        #
        print "    }\n}"
    fi

    log_return_msg 0 "$0()" "$LINENO"
    return $?
} # End of "SerializeAsArray()"


#=============================================================================
#
# Name:        SerializeAsAssociativeArray
#
# Description: This function is called when the query action is invoked in
#              such a way that the data set is non-simple, and is stored in
#              an associative array. By default, the output is in standard
#              "ATTR=VALUE" pairs, one per line, with the values doublequoted.
#              Optionally, the user may request colon-delimited or XML-
#              formatted output. Also optionally, the user may specify one
#              or more specific attributes that they want to see, in which
#              case *only* those attributes are displayed. It should be noted
#              that the order in which the user specifies those attributes is
#              *not* honored.
#
#              It is important to note the format of the input data in the
#              associative array. Since more than one object might be
#              represented in that data set, and those objects should be
#              displayed logically (i.e. together, and not interleaved with
#              the data of the other objects), there was a need to
#              differentiate the data sets within the array. Unfortunately,
#              ksh93 does not support multi-dimensional arrays, which are
#              the logical solution for this type of problem. So for better
#              or worse, the original designers of clvt chose to simulate
#              a multi-dimensional array with the input hash by optionally
#              appending index numbers to the keys.
#
#              The index numbers indicate which object that key/value belongs
#              to, and are used to display the data together, in logical
#              groupings. This adds a ton of complexity to this function...
#              but seems to work. The only problem is when the key value
#              naturally ends in a digit itself (i.e. where the digit is
#              actually part of the attribute name), in which case it is
#              impossible to determine where the key value ends and the
#              assigned index number (if any), begins! To handle this
#              situation, a pound/comment/hash symbol can be inserted
#              between the core key value and its index number. For example,
#              given a key name of "SITE1" and an index value of "3", the
#              key would need to look like this:  "SITE1#3"
#
# Inputs:      array   A reference to the pre-populated associative array
#                      that contains the data to be formatted and displayed.
#
#              class   The class, or type, of the data that was passed in,
#                      such as "cluster", "site", "node", etcetera.
#
#              CLMGR_ATTRS  Optional. A set of attributes to limit the output
#                           to (no error occurs if they are not found).
#
#              CLMGR_DELIMITER  Optional. If set (to any value), the output
#                           will be delimited by the value in this variable.
#
#              CLMGR_COLON  Optional. If set (to any value), the output will
#                           be colon-delimited.
#
#              CLMGR_XML    Optional. If set (to any value), the output will
#                           be formatted in XML.
#
# Outputs:     The data in the provided associative array is displayed in the
#              appropriate format.
#
# Returns:     0 if no problems are encountered.
#              1 if something goes wrong.
#
#=============================================================================
function SerializeAsAssociativeArray {
    . $HALIBROOT/log_entry "$0()" "$CL" max

    typeset -n array=$1
    typeset -u class=$2
    typeset -l lcclass=$class

    typeset -u upper_key="" upper_attr=""
    typeset -l lower_key=""
    typeset -A CHILDREN
    typeset -i SNUM=0 COUNT=0 I=0 J=1 DATA_LAYER=-1 LAST_DATA_LAYER=-1 ATTR_COUNT=0
    typeset key= value= attributes= INDENT= PARENTS= INDEXES= attr=

    if (( ${#array[*]} == 0 )); then
        : No data was provided. The properties array is empty.
        log_return_msg "$RC_ERROR" "$0()" "$LINENO"
        return $?  # No data was provided!
    fi

    #==================================================
    # If conflicting settings, give preference to XML
    #==================================================
    (( CLMGR_XML && CLMGR_COLON )) && CLMGR_COLON=0

    if [[ $CLMGR_DELIMITER == *([[:space:]]) ]] && \
       (( CLMGR_COLON ))
    then
        CLMGR_DELIMITER=":"
    fi

    #==========================================================
    # Establish the appropriate attribute sets for this class
    #==========================================================
    typeset NEW_CLMGR_ATTRS=
    for value in $CLMGR_ATTRS; do
        if [[ $value != *=* ]]; then
            NEW_CLMGR_ATTRS="$NEW_CLMGR_ATTRS $value"
        fi

        # Slow down the loop a fraction, to avoid spiking the CPU
        LANG=C sleep .0001
    done
    CLMGR_ATTRS=$NEW_CLMGR_ATTRS
    CLMGR_ATTRS=${CLMGR_ATTRS//+([[:space:]])/ }
    CLMGR_ATTRS=${CLMGR_ATTRS# }
    CLMGR_ATTRS=${CLMGR_ATTRS% }
    typeset -i matches=0
    if [[ -n $CLMGR_ATTRS ]]; then
        for upper_attr in $CLMGR_ATTRS; do
            matches=0
            typeset ATTR_SEQ=${_ATTR_ORDER[$lcclass]}
            if [[ $ATTR_SEQ == *([[:space:]]) || \
                  $CLMGR_DELIMITER != *([[:space:]]) ]]
            then
                ATTR_SEQ=${_COLON_ATTR_ORDER[$lcclass]}
            fi
            for upper_key in $ATTR_SEQ; do
                if [[ $upper_key == $upper_attr ]]; then
                    attributes="$attributes $upper_key"
                    (( matches++ ))
                    (( ATTR_COUNT++ ))
                fi

                # Slow down the loop a fraction, to avoid spiking the CPU
                LANG=C sleep .0001
            done

            # Slow down the loop a fraction, to avoid spiking the CPU
            LANG=C sleep .0001
        done
        attributes=${attributes# }
    else
        attributes=${_ATTR_ORDER[$lcclass]}
        if [[ $attributes == *([[:space:]]) || \
              $CLMGR_DELIMITER != *([[:space:]]) ]]
        then
            attributes=${_COLON_ATTR_ORDER[$lcclass]}
        fi
    fi

    #==================================================================
    # Check for any dynamic/non-static attributes, meaning attributes
    # that are added at runtime, and are not pre-defined in the
    # ATTR_ORDER structure. One place where these are used is in the
    # snapshot query code, in KLIB_HACMP_get_snapshot_attributes.
    #==================================================================
    for key in ${!array[*]}; do
        if [[ $key == \~* ]]; then
            base=${key%%+([0-9])}  # Covers most cases
            if [[ $base == *+([0-9])\. ]]; then
                base=${base%%+(+([0-9])\.)}
            fi

            if [[ " $attributes " != *\ $base\ * ]]; then
                attributes="$attributes $base"
            fi
        fi
    done

    #=============================================================
    # Extract the index numbers, if any, from the provided data.
    # The indexes are used to logically group different subsets
    # of data within the overall set.
    #=============================================================
    for key in ${!array[*]}; do

        if [[ $key == *_V4*([[:digit:]]) ]]; then
            base="${key%_V4*}_V4"
        else
            base=${key%%+([0-9])}  # Covers most cases
            if [[ $base == *+([0-9])\. ]]; then
                base=${base%%+(+([0-9])\.)}
            fi
        fi
        index=${key#$base}
        [[ -z $index ]] && index=0

        #=================================================================
        # All attributes definitions are *supposed* to be in uppercase.
        # However, this code *guarantees* that a typo will not break us.
        #=================================================================
        if [[ $key != \~* ]]; then
            value=${array[$key]}
            unset array[$key]
            upper_key=$base
            array[$upper_key$index]=$value
        else
            # Dynamic/Non-static keys are case-sensitive
            value=${array[$key]}
            unset array[$key]
            key=$base
            array[$key$index]=$value
        fi

        #======================================================================
        # If any search criteria were specified (i.e. via CLMGR_ATTRS), then
        # get rid of any retrieved data that does *not* fit the criteria.
        # If the user didn't specifically ask for it, then we don't need it!
        # If no search is being performed, then store the indexes and base
        # attribute names with newline separators, so that they can be easily
        # sorted later (after this loop completes).
        #======================================================================
        matches=0
        for upper_key in $CLMGR_ATTRS; do
            [[ $base == $upper_key* ]] && (( matches++ ))
        done
        if [[ -n $CLMGR_ATTRS ]] && (( matches == 0 )); then
            (( CLMGR_JSON )) && [[ $base == *NAME ]] && continue  # JSON _must_ have the object name
            unset array[$base$index]
        elif [[ -n $index ]]; then
            INDEXES="$INDEXES\n$index"
        fi

        # Slow down the loop a fraction, to avoid spiking the CPU
        LANG=C sleep .0001
    done
    INDEXES=${INDEXES#\\n}

    #==================================================================
    # Sort the indexes (removing all duplicates), so that everything
    # gets displayed in logical order. Remember that the input data
    # is in an associative array, which always scrambles the order of
    # the keys.
    #==================================================================
    if [[ $INDEXES != *([[:space:]]) ]]; then
        print -- "$INDEXES" |\
        sort -u -t. -k1,1n -k2,2n -k3,3n -k4,4n -k5,5n |\
        while read INDEX; do
            [[ $INDEX == *([[:space:]]) ]] && continue
            INDEXES[$I]=$INDEX

            DOTS=${INDEX//[[:digit:]]/}
            (( DATA_LAYER = ${#DOTS} ))
            (( CHILDREN[$DATA_LAYER]++ ))
            (( I++ ))

            # Slow down the loop a fraction, to avoid spiking the CPU
            LANG=C sleep .0001
        done
    fi

    #=====================================================================
    # To ease the somtimes monstrous complexity of displaying properly
    # formatted XML in a friendly-for-the-humans manner, establish the
    # appropriate parent element names in advance. Currently, the "node"
    # class is the only one that allows for multiple layers of depth.
    #=====================================================================
    if (( CLMGR_XML || CLMGR_JSON )); then
        case $class in
            STORAGE) PARENTS[0]="$class"        ;;
                *SS) PARENTS[0]="${class}ES"    ;;
                 *Y) PARENTS[0]="${class%Y}IES" ;;
                  *) PARENTS[0]="${class}S"     ;;
        esac
        PARENTS[1]=$class

        if [[ ${!CHILDREN[*]} != *([[:space:]]) ]]; then
            for layer in ${!CHILDREN[*]}; do
                (( layer++ ))
                case $layer in
                    1) case $class in
                           STORAGE)
                               if [[ ${_ENV_ARGS[TYPE]} == "family" ]]; then
                                   PARENTS[$layer]="CLASS"
                               else
                                   PARENTS[$layer]="PHYSICAL_VOLUME"
                               fi                    ;;
                           *) PARENTS[$layer]=$class ;;
                       esac
                    ;;
                    2) case $class in
                           FILE_COLLECTION)        PARENTS[$layer]=FILE_DATA       ;;
                           NODE)                   PARENTS[$layer]=ADAPTER         ;;
                           NETWORK)                PARENTS[$layer]=INTERFACE_DATA  ;;
                           APPLICATION_CONTROLLER) PARENTS[$layer]=MONITORING_DATA ;;
                           *)                      PARENTS[$layer]=$class$layer    ;;
                       esac
                    ;;
                    *) PARENTS[$layer]=$class$layer ;;
                esac

                # Slow down the loop a fraction, to avoid spiking the CPU
                LANG=C sleep .0001
            done
        else
            PARENTS[1]=$class
        fi
    fi

    INDENT="    "
    if (( CLMGR_XML )) && [[ $class == "CLUSTER" ]]
    then
        INDENT="$INDENT    "
    fi

    if (( CLMGR_JSON ))
    then
        if [[ $CLMGR_COMMAND != *\ -v\ *query\ cluster* ]]
        then
            : Since JSON output was requested, print our standard clmgr JSON header
            print "{\n$INDENT\"clmgr\": {"
        fi
        INDENT="$INDENT    "

        #
        : Create an array, MAX_ATTR_COUNT, to store the maximum number of objects
        : found in the data set at each layer of the data, and for each object.
        : Confusing, right? It helps to think of the data visually, with child
        : elements indented below parent elements... with the added complication
        : that the number of child elements can vary from object to object. For
        : an example, consider the files for each file collection. One collection
        : might have many files and another might have only a few.
        :
        : For each level/layer/depth of indentation for each object, we count the
        : total number of objects in that location. This is really only useful for
        : the JSON output, though, and for that it is absolutely essential in order
        : to get the formatting right. The last object at any depth must not have a
        : trailing comma, but all preceding objects at that same depth _must_ have
        : a trailing comma.
        #
        integer CNT=0
        for (( I=0; I<${#INDEXES[*]}; I++ )); do
            INDEX=${INDEXES[$I]}

            #
            : The way clmgr works, indented/child data is specified via indexes
            : that have decimal points. So if a parent object has an index of
            : 5, as an example, to display any indented child values for that
            : object, you would simply append the new index number to the parent
            : index, such as "5.1", "5.2", "5.3", ... Why not just use
            : multi-dimensional arrays you might ask... Well, because they did
            : not exist in the version of Korn shell shipped with older versions
            : of AIX, so the CLAM team came up with this. So for now, just be
            : aware of how it works.
            #
            DOTS=${INDEX//[[:digit:]]/}

            integer OBJNUM=${INDEX%\.*}

            #
            : For classes that support multiple "depths", meaning they support
            : sub-sections of attributes per object, the number of attributes
            : must be counted individually for each object. The reason for that
            : is that the sub-sections can have variable length. so for those
            : classes, we must count each unique index number to get an accurate
            : count. For all other classes, which are more simple, we can just
            : count the number of non-null attributes used in the output.
            #
            if [[ $class == @(FILE_COLLECTION|APPLICATION_CONTROLLER)* ]]
            then
                : Increment the count
                CNT=${MAX_ATTR_COUNT[$OBJNUM][${#DOTS}]}
                (( CNT++ ))
                MAX_ATTR_COUNT[$OBJNUM][${#DOTS}]=$CNT

            else
                for key in $attributes; do
                    if [[ $key != \~* ]]; then
                        upper_key=$key
                        key=$upper_key
                    fi
                    if [[ ! ${!array[$key$INDEX]} || \
                        " ${!array[@]} " != *\ $key$INDEX\ * || \
                        ${!array[$key$INDEX]} == *null* ]]
                    then
                        continue
                    fi
                    CNT=${MAX_ATTR_COUNT[$OBJNUM][${#DOTS}]}
                    (( CNT++ ))
                    MAX_ATTR_COUNT[$OBJNUM][${#DOTS}]=$CNT
                done
            fi
        done
    fi

    if (( CLMGR_XML ))
    then
        if [[ $class != "CLUSTER" || $CLMGR_COMMAND == *\ -v\ * ]]
        then
            : Display the outermost opening XML element, which is the class name
            print "<${PARENTS[0]}>"
        else
            INDENT=""
        fi

    elif (( CLMGR_JSON )); then
        : Display the opening tree leaf, which is the class name
        typeset -l class_lc=${PARENTS[0]}
        [[ $class_lc == "clusters" ]] && class_lc=${class_lc%s}
        print "$INDENT\"$class_lc\": {"
    fi

    #
    : Count the total number of objects being displayed. "TOTAL_OBJECTS" is
    : a two-dimensional array used for counting the number of objects to be
    : displayed. The first element, when set to zero, counts the base objects.
    : When set to one, the optional second element then becomes the object
    : number and is used to count the number of child object groupings. As an
    : example of the most complicated case, consider file collections. The
    : "base objects" are obviously the file collections themselves, and we
    : add those up in TOTAL_OBJECTS[0]. However, they have child groupings
    : comprised of each file managed by that file collection, plus its size.
    : Since the number of those child groupings varies from collection to
    : collection, they are added up individually using the parent objects
    : object number via TOTAL_OBJECTS[1][$OBJNUM]. It is complicated... but
    : it really does all work.
    #
    typeset TOTAL_OBJECTS
    if (( CLMGR_JSON ))
    then
        if [[ ${PARENTS[0]} != CLUSTER* ]]; then
            COUNT=0
            INDENT="$INDENT    "
            [[ -z $CLMGR_ATTRS ]] && print -n "$INDENT\"objects\": [ "
            for key in ${!array[*]}; do
                #
                : Each class type that has child groupings must be procesed
                : a little differently from the "normal", flatter classes.
                #
                if [[ $class == "FILE_COLLECTION" && $key == FILE+([0-9])\.+([0-9]) ]] || \
                   [[ $class == "APPLICATION_CONTROLLER" && $key == RESOURCE_GROUP+([0-9])\.+([0-9]) ]]
                then
                    #
                    : Add up just one of the attributes from each child
                    : grouping to determine the total number of sub-sections
                    : that will be displayed for this class+object.
                    #
                    typeset OBJNUM=${key#@(FILE|RESOURCE_GROUP)}
                            OBJNUM=${OBJNUM%%\.*}
                    if [[ $OBJNUM == +([0-9]) ]]
                    then
                        TOTAL_OBJECTS[1][$OBJNUM]=$(( ${TOTAL_OBJECTS[1][$OBJNUM]} + 1 ))
                    fi

                elif [[ $class == "HOST" && $key == HOSTNAME+([0-9]) ]] || \
                     [[ $class != "HOST" && $key == NAME+([0-9])     ]]
                then
                    #
                    : Add up just one of the attributes from each parent/base
                    : object. For all classes except one, the attribute "NAME"
                    : is provided and can be used for this. The exception is
                    : the "host" class, which has "HOSTNAME" instead.
                    #

                    if [[ -z $CLMGR_ATTRS ]]
                    then
                        : Display the label/name of each base object
                        (( COUNT > 0 )) && print -n ", "
                        if [[ $class == "DEPENDENCY" ]]
                        then
                            INDEX=${key#NAME}
                            print -n "\"${array[$key]} (${array[TYPE$INDEX]})\""
                        else
                            print -n "\"${array[$key]}\""
                        fi
                    fi

                    (( COUNT++ ))
                    TOTAL_OBJECTS[0]=$(( ${TOTAL_OBJECTS[0]} + 1 ))
                fi
            done
            [[ -z $CLMGR_ATTRS ]] && print " ],"
        else
            TOTAL_OBJECTS[0]=1
        fi
    fi

    #
    # Indicates if there are "indented" values, such as with file collections.
    # For most object classes in clmgr, the depth is zero; there are no sub-
    # groupings of attributes for some parent object. Some exceptions to this
    # are file collections and application controllers.
    #
    # NOTE: Do not declare these as integers, even though that is ultimately
    #       what they will contain. There is some code down below that actually
    #       checks for a couple of them having a null value, and if they never
    #       do, bad things happen (like the delimited output column headers
    #       never get displayed).
    #
    typeset DATA_DEPTH=""
    typeset LAST_DATA_DEPTH=""
    typeset NEXT_DATA_DEPTH=""

    integer NEED_OPENER=1
    typeset QUEUE=""        # Used to provide accurate closing elements
    typeset NEXT_INDEX=""
    typeset DOTS=""         # Used to determine DATA_DEPTH

    #
    # OBJ_COUNT is a two-dimensional array used to keep track of the total
    # number of object stanzas that are to be displayed at a particular
    # level.
    #
    typeset OBJ_COUNT

    #
    # CURRENT_ATTR_COUNT is a two-dimensional array used to keep track of
    # which object we are currently processing and displaying at a particular
    # data depth (usually zero).
    #
    typeset CURRENT_ATTR_COUNT

    for (( I=0; I<${#INDEXES[*]}; I++ )); do
        INDEX=${INDEXES[$I]}

        : Extract the unique object number from the index
        integer OBJNUM=${INDEX%\.*}

        if (( I <= ${#INDEXES[*]} )); then
            NEXT_INDEX=${INDEXES[$I+1]}
            DOTS=${NEXT_INDEX//[[:digit:]]/}
            NEXT_DATA_DEPTH=${#DOTS}
        else
            unset NEXT_INDEX
            unset NEXT_DATA_DEPTH
        fi

        LAST_DATA_DEPTH=$DATA_DEPTH
        DOTS=${INDEX//[0-9]/}  # Remove index numbers, leaving only the "dot" separators (if any)
        DATA_DEPTH=${#DOTS}    # The number of dots/decimals (if any) is the data depth. Usually zero.

        : Initialize the object counter for indent level $DATA_DEPTH
        CURRENT_ATTR_COUNT[$OBJNUM][$DATA_DEPTH]=0

        if (( CLMGR_JSON ))
        then
            if [[ ${PARENTS[0]} == @(FILE_COLLECTION|APPLICATION_CONTROLLER)* ]]
            then
                if (( DATA_DEPTH == 1 && NEED_OPENER ))
                then
                    #
                    : For classes that provide indented child sections,
                    : an appropriate opener must be displayed.
                    #

                    if [[ ${PARENTS[0]} == FILE_COLLECTION* ]]
                    then
                        : Begin the set of files for this collection
                        INDENT="$INDENT    "
                        print "$INDENT\"FILES\": ["

                    elif [[ ${PARENTS[0]} == APPLICATION_CONTROLLER* ]]
                    then
                        : Begin the set of resource groups for this application
                        INDENT="$INDENT    "
                        print "$INDENT\"RESOURCE_GROUPS\": ["
                    fi
                    NEED_OPENER=0

                elif (( DATA_DEPTH == 0 && ! NEED_OPENER ))
                then
                    : Close out the set of child stanzas for this object
                    INDENT=${INDENT%    }
                    print "$INDENT    ]\n$INDENT},"
                    NEED_OPENER=1
                fi
            fi
        fi

        if (( CLMGR_XML || CLMGR_JSON ))
        then
            INDENT=
            for (( j=0; j<$DATA_DEPTH+1; j++ )); do
                if [[ $class != "CLUSTER" || $CLMGR_COMMAND == *\ -v\ * ]]
                then
                    INDENT="$INDENT    "
                fi

                # Slow down the loop a fraction, to avoid spiking the CPU
                LANG=C sleep .0001
            done
        fi

        #===================================================
        # Print an opening element and add it to the queue
        #===================================================
        if (( CLMGR_XML )); then
            print "$INDENT<${PARENTS[$DATA_DEPTH+1]}>"

            if [[ ${PARENTS[$DATA_DEPTH+1]} == "CLUSTER" || $CLMGR_COMMAND == *\ -v\ * ]]
            then
                INDENT="$INDENT    "
            fi

            if [[ -z $QUEUE ]]; then
                QUEUE=$INDEX
            else
                QUEUE="$QUEUE $INDEX"
            fi

        elif (( CLMGR_JSON )); then
            INDENT="$INDENT    "

            if [[ ${PARENTS[0]} == CLUSTER* ]]
            then
                #
                : The cluster class is special, since it has no objects. It
                : _is_ the object. So it gets handled a little differently.
                #
                if [[ $CLMGR_COMMAND != *\ -v\ * ]]
                then
                    INDENT="$INDENT    "
                fi
                OBJ_COUNT[0]=$(( ${OBJ_COUNT[0]} + 1 ))

            elif (( DATA_DEPTH == 0 ))
            then
                INDENT="$INDENT    "

                : Display the base object name
                if [[ $class == "DEPENDENCY" ]]
                then
                    #
                    : Dependency names are not unique without their type
                    : being associated with them, so we have to do that
                    : here.
                    #
                    print "$INDENT\"${array[NAME$INDEX]} (${array[TYPE$INDEX]})\": {"

                elif [[ $class == "HOST" ]]
                then
                    #
                    : The "host" class is the _only_ class that does not use
                    : an identifying attribute of "NAME". So we display the
                    : "HOSTNAME" attribute instead. ~sigh~
                    #
                    print "$INDENT\"${array[HOSTNAME$INDEX]}\": {"
                else
                    print "$INDENT\"${array[NAME$INDEX]}\": {"
                fi

                #
                : For each base object we display, bump the object counter by
                : one. This will be compared to the total possible objects to
                : determine how to close out each section, with or without a
                : trailing comma.
                #
                OBJ_COUNT[$DATA_DEPTH][0]=$(( ${OBJ_COUNT[$DATA_DEPTH][0]} + 1 ))

            elif [[ ${PARENTS[0]} == @(FILE_COLLECTION|APPLICATION_CONTROLLER)* ]]
            then
                : Begin a single child group, such as FILE and SIZE for file collections
                INDENT="$INDENT    "
                print "$INDENT    {"
                INDENT="$INDENT    "
            fi

            if (( DATA_DEPTH > 0 ))
            then
                #
                : If an indented child grouping is currently being displayed,
                : such as the FILE and SIZE pairings for file collections, bump
                : the child grouping counter by one. This keeps track of the
                : groupings/stanzas themselves, and not each individual
                : attribute. It is used to determine if the final grouping in
                : the indentation has been reached or not. If so, no comma is
                : needed. Otherwise, we need a comma.
                #
                OBJ_COUNT[$DATA_DEPTH][$OBJNUM]=$(( ${OBJ_COUNT[$DATA_DEPTH][$OBJNUM]} + 1 ))
            fi

        elif [[ $CLMGR_DELIMITER != *([[:space:]]) ]]; then
            #==================================================================
            # Attempt to detect if the underlying data has fundamentally
            # changed based on upon the size of the current index, relative
            # to the last index. If changed, display the appropriate headers.
            #==================================================================
            if [[ -z $LAST_DATA_DEPTH ]] || \
               (( $DATA_DEPTH != $LAST_DATA_DEPTH ))
            then
                [[ -n $LAST_DATA_DEPTH ]] && print
                if (( ! CLMGR_SUPPRESS )); then
                    display_colon_headers "${!array[*]}" "$attributes" "$INDEX"
                fi
            fi

        elif [[ $CLMGR_DELIMITER == *([[:space:]]) && -n $LAST_DATA_DEPTH ]] && \
             (( $DATA_DEPTH != $LAST_DATA_DEPTH ))
        then
            print  # Default display; a little whitespace improves readability
        fi

        #
        : Determine which attributes will actually be displayed, which is
        : often a subset of the total available attributes for a given class,
        : and count them up. This information is used to determine when the
        : last attribute of a given stanza for an object has been displayed.
        #
        typeset TOTAL_ATTRS=0  # The total number of per-object attributes to be displayed
        for key in $attributes; do
            (( TOTAL_ATTRS++ ))
        done
        for key in $attributes; do
            if [[ $key != \~* ]]; then
                upper_key=$key
                key=$upper_key
            fi

            if [[ ! ${!array[$key$INDEX]} || \
                  " ${!array[@]} " != *\ $key$INDEX\ * || \
                  ${!array[$key$INDEX]} == *null* ]]
            then
                #
                : The dependency class is an odd ball, and does not follow
                : the rules like most of the other classes. As such, it has
                : a hard-coded total, set below, and we should not adjust it
                : here.
                #
                if [[ $class != @(DEPENDENCY|EVENT) ]]
                then
                    #
                    : Since this attribute is not present in the data set, do
                    : not count it towards the total attribute count that will
                    : actually be displayed.
                    #
                    (( TOTAL_ATTRS-- ))
                fi
            fi
        done

        #================================================================
        # Display the complete set of data for the "$INDEX" element/set
        # The "$attributes" loop ensures that any user-specified attrs
        # are the only ones displayed. Also, in the case where the user
        # did not specify any attributes, and all attributes are used,
        # this loop guarantees the order the attributes are displayed.
        #================================================================
        COUNT=0
        for key in $attributes; do
            if [[ $key != \~* ]]; then
                upper_key=$key
                key=$upper_key
            fi

            #
            # Skip over any undefined attributes; have to control the output.
            # Just because the FPATH function stuffed something in the array
            # doesn't mean we should display it! Only _ATTR_ORDER gets to
            # decide what is displayed, and in what order. Had to add the
            # explicit test for "*null*" to accommodate the ksh93 shipped
            # with AIX 7.1. *sigh*
            # With ksh93 shipped with AIX 7.3, null check is not working.
            #
            if [[ ! ${!array[$key$INDEX]} || \
                  " ${!array[@]} " != *\ $key$INDEX\ * || \
                  ${!array[$key$INDEX]} == *null* ]]
            then
                continue
            fi

            value=${array[$key$INDEX]}
            value=${value##+([[:space:]])}
            value=${value%%+([[:space:]])}

            : Increment the object counter for indent level $DATA_DEPTH
            CURRENT_ATTR_COUNT[$OBJNUM][$DATA_DEPTH]=$(( ${CURRENT_ATTR_COUNT[$OBJNUM][$DATA_DEPTH]} + 1 ))

            #
            : For resource group dependencies, there is no consistency about
            : the total number of attributes to display per object, since it
            : depends on the type of the dependeny. So we override it with a
            : proper, hard-coded value here. The same thing is needed for
            : displaying events, custom versus pre-defined.
            #
            if [[ $class == "DEPENDENCY" && $key == TYPE* ]]
            then
                case $value in
                    DIFF*)   TOTAL_ATTRS=5 ;;
                    SAME*)   TOTAL_ATTRS=4 ;;
                    STOP*)   TOTAL_ATTRS=4 ;;
                    START*)  TOTAL_ATTRS=4 ;;
                    PARENT*) TOTAL_ATTRS=4 ;;
                    *)       TOTAL_ATTRS=3 ;;
                esac

            elif [[ $class == "EVENT" && $key == TYPE* ]]
            then
                case $value in
                    CUSTOM)  TOTAL_ATTRS=4 ;;  # Custom events have 4 output lines per event
                    *)       TOTAL_ATTRS=7 ;;  # Pre-defined events have 7 output lines per event
                esac
            fi

            #
            : If this is a dynamic/non-static output, remove
            : the leading tilde that indicates that fact
            #
            key=${key#\~}

            if (( CLMGR_XML )); then
                print -- "$INDENT<${key%#}>$value</${key%#}>"

            elif (( CLMGR_JSON )); then
                if (( COUNT < ( TOTAL_ATTRS - 1 ) ))
                then
                    : This is just a normal, inline property, not the last one in the grouping
                    print -- "$INDENT    \"${key%#}\": \"$value\","

                elif (( $DATA_DEPTH == 0 )) && [[ -n ${OBJ_COUNT[0][0]} ]] && \
                     (( ${OBJ_COUNT[0]} < ${TOTAL_OBJECTS[0]} )) && \
                     [[ $NEXT_INDEX != +([0-9])\.+([0-9]) ]]
                then
                    #
                    : The end of a base object has been detected, and there
                    : are no indented child objects for this object, and this
                    : is not the last of the base objects that will be
                    : displayed. Display the closing bracket with a comma,
                    : which indicates that another object will be displayed
                    : after this one.
                    #
                    print -- "$INDENT    \"${key%#}\": \"$value\"\n${INDENT}},"

                elif (( $DATA_DEPTH == 1 )) && [[ -n ${OBJ_COUNT[1][$OBJNUM]} ]] &&  \
                     (( ${OBJ_COUNT[1][$OBJNUM]} < ${TOTAL_OBJECTS[1][$OBJNUM]} ))
                then
                    #
                    : The end of an indented group of child stanzas has been
                    : detected, but it is not the last stanza in the group.
                    : Display the closing bracket with a comma, which indicates
                    : that another grouping will be displayed after this one.
                    #
                    print -- "$INDENT    \"${key%#}\": \"$value\"\n${INDENT}},"

                elif [[ $NEXT_INDEX != +([0-9])\.+([0-9]) ]]
                then
                    #
                    : The last base or indented, child object has just finished
                    : being displayed. Close it out with a closing bracket with
                    : no comma, to indicate that it is the last object.
                    #
                    print -- "$INDENT    \"${key%#}\": \"$value\"\n$INDENT}"
                    INDENT=${INDENT%    }

                fi

            elif [[ $CLMGR_DELIMITER != *([[:space:]]) ]]; then
                (( COUNT )) && print -n "$CLMGR_DELIMITER"
                print -n -- "$value"
            else
                print -- "${key%\#}=\"$value\""
            fi

            (( COUNT++ ))

            # Slow down the loop a fraction, to avoid spiking the CPU
            LANG=C sleep .0001
        done  # End of the "attributes" loop

        ROOT=${INDEX%\.*}
        [[ $ROOT == $INDEX ]] && ROOT=0
        NEXT_ROOT=${NEXT_INDEX%\.*}
        [[ $NEXT_ROOT == $NEXT_INDEX ]] && NEXT_ROOT=0

        #====================================================
        # Print a closing element, if appropriate/necessary,
        # and check/clean-up the queue.
        #====================================================
        # A missing "NEXT_INDEX" means all data has been
        # displayed, so close out all remaining elements
        # on the queue. A shallower data depth for the
        # next element to be displayed indicates that all
        # queued elements back to that depth should be
        # closed out.
        #====================================================
        if [[ -z $NEXT_INDEX ]] || \
           (( $NEXT_DATA_DEPTH < $DATA_DEPTH ))
        then
            if (( CLMGR_XML || CLMGR_JSON )); then
                for (( J=$DATA_DEPTH; J>=$NEXT_DATA_DEPTH; J-- )); do
                    if (( CLMGR_XML ))
                    then
                        INDENT=${INDENT%    }
                        print "$INDENT</${PARENTS[$DATA_DEPTH+1]}>"

                    elif (( CLMGR_JSON ))
                    then
                        if (( ${OBJ_COUNT[0]} == ${TOTAL_OBJECTS[0]} ))
                        then
                            if [[ ${PARENTS[0]} == @(FILE_COLLECTION|APPLICATION_CONTROLLER)* ]] &&
                               (( J == 1 ))
                            then
                                : The indentation level here requires special handling
                                print "${INDENT}]"
                                INDENT=${INDENT%    }  # Finished a block. Reduce the indentation.
                                print "${INDENT}}"
                            elif [[ $class != "CLUSTER" ]]
                            then
                                if [[ ${PARENTS[0]} == @(FILE_COLLECTION|APPLICATION_CONTROLLER)* ]]
                                then
                                    INDENT=${INDENT%    }
                                fi
                                print "${INDENT}}"
                                INDENT=${INDENT%    }  # Finished a block. Reduce the indentation.
                            else
                                INDENT=${INDENT%    }
                            fi
                        fi
                    fi

                    QUEUE=${QUEUE%\ *}

                    LAST=${QUEUE##*\ }
                    DOTS=${LAST//[[:digit:]]/}
                    DATA_DEPTH=${#DOTS}

                    # Slow down the loop a fraction, to avoid spiking the CPU
                    LANG=C sleep .0001
                done
            fi

        #======================================================
        # Same depth and same root means the current data set
        # has been displayed, and should be closed out.
        #======================================================
        elif (( $DATA_DEPTH == $NEXT_DATA_DEPTH )) && \
             [[ $ROOT == $NEXT_ROOT ]]
        then
            if (( CLMGR_XML )); then
                print "$INDENT</${PARENTS[$DATA_DEPTH+1]}>"
                QUEUE=${QUEUE%\ *} # Remove last element from queue

            elif (( CLMGR_JSON )); then
                QUEUE=${QUEUE%\ *} # Remove last element from queue

            elif [[ $CLMGR_DELIMITER == *([[:space:]]) ]]; then
                if [[ -z $CLMGR_ATTRS ]] || (( ATTR_COUNT > 1 )); then
                    print  # Default display; whitespace improves readability
                fi
            fi
        fi

        if [[ $CLMGR_DELIMITER != *([[:space:]]) ]] && \
           (( COUNT && ${#array[*]} > 0 ))
        then
             print
        fi

        # Slow down the loop a fraction, to avoid spiking the CPU
        LANG=C sleep .0001
    done

    #
    : Close out the data display as appropriate for the output format.
    # For the default attr=value pair output, there is no close-out needed.
    #
    if (( CLMGR_XML )); then
        if [[ $class != "CLUSTER" || $CLMGR_COMMAND == *\ -v\ * ]]
        then
            : Display the outermost closing element
            print "</${PARENTS[0]}>"
        fi

    elif (( CLMGR_JSON )); then
        if [[ $CLMGR_COMMAND != *\ -v\ *query\ cluster* ]]
        then
            if [[ $class == "CLUSTER" && $CLMGR_COMMAND == *\ -v\ * ]]
            then
                : Display the closing bracket for the cluster data segment
                # NOTE: We don't need two here because the "CLUSTER" class
                #       contains no objects; the cluster _is_ the object.
                print "}"
            else
                : Display the outermost closing brackets
                print "    }\n}"
            fi
        fi

    elif [[ $CLMGR_DELIMITER != *([[:space:]]) ]]; then
        : For delimited data, we close out with a blank line
        print
    fi

    log_return_msg 0 "$0()" "$LINENO"
    return $?
} # End of "SerializeAsAssociativeArray()"


#=============================================================================
#
# Name:        display_colon_headers
#
# Description: If colon-delimited output was requested, and column header
#              suppression was *not* asked for, then display the appropriate
#              column headers.
#
# Inputs:      key_list     a list of all keys from the data collected
#                           for the specified object/class.
#
#              attributes   a list of all allowable attributes for the
#                           class of object currently being processed.
#
#              INDEX        the current index
#
# Outputs:     A colon-delimited header line is displayed on STDOUT.
#
# Returns:     0 if a header line was displayed, or
#              1 if nothing was displayed
#
#=============================================================================
function display_colon_headers {
    . $HALIBROOT/log_entry "$0()" "$CL" max

    typeset key_list=$1
    typeset attributes=$2
    typeset INDEX=$3

    typeset -i COUNT=0 STATUS=1
    typeset LINE=

    if [[ $CLMGR_DELIMITER == *([[:space:]]) ]] && \
       (( CLMGR_COLON ))
    then
        CLMGR_DELIMITER=":"
    fi

    if [[ $CLMGR_DELIMITER == *([[:space:]]) ]] || \
       (( CLMGR_SUPPRESS ))
    then
        return
    fi

    LINE="#"
    [[ $CLMGR_GUI != "SMIT" ]] && LINE="$LINE "

    for upper_key in $attributes; do
        [[ " $key_list " != *\ $upper_key$INDEX\ * ]] && continue

        upper_key=${upper_key#\~}
        upper_key=${upper_key%#}

        (( COUNT )) && LINE="$LINE$CLMGR_DELIMITER"
        LINE="$LINE$upper_key"
        (( COUNT++ ))

        # Slow down the loop a fraction, to avoid spiking the CPU
        LANG=C sleep .001
    done

    if (( ${#LINE} > 2 )); then
        print -- "$LINE"
        STATUS=0
    fi

    log_return_msg "$STATUS" "$0()" "$LINENO"
    return $?
} # End of "display_colon_headers()"


###############################################################################
#
# Name:        runClassProcessor
#
# Description:
#
# Inputs:      class           The object class for the operation being
#                              attempted.
#              substitutions   A list of the arguments passed in.
#
# Outputs:
#
# Returns:
#
###############################################################################
function runClassProcessor {
    . $HALIBROOT/log_entry "$0()" "$CL" max

    typeset class=$1
    typeset -n substitutions=$2
    typeset -n _ENV_ARGS=$3
    [[ -n $4 ]] && typeset -n properties=$4

    typeset -i rc=-1
    typeset value=

    CL=$LINENO . $HAEVENTS/class_processors "$_ACTION"
    typeset processor="${_CLASS_PROCESSORS[$class]} "
    if [[ $processor == *([[:space:]]) ]]; then
        [[ " $CLASSES " != *\ $class\ * ]] && class=${class#*_}
        print "$SENTINEL COMMAND  ($CLMGR_TRANSACTION_ID):  ${CLMGR_COMMAND/ -T $CLMGR_TRANSACTION_ID}" >>$CLMGR_TMPLOG
        print "$SENTINEL INTERNAL ($CLMGR_TRANSACTION_ID):  NOT FOUND!!" >>$CLMGR_TMPLOG
        cl_dspmsg -s $CLMGR_SET $CLMGR_MSGS 183 '\nERROR: unrecognized %1$s operation attempted: %2$s\n\n' "$CLMGR_PROGNAME" "$CLMGR_PROGNAME $_ACTION $class" 1>&2
        log_return_msg "$RC_INCORRECT_INPUT" "$0()" "$LINENO"
        return $?
    fi
    processor=${processor//+([[:space:]])/ }

    #=======================================================
    # Perform variable substitution on the class processor
    #=======================================================
    for var in $substitutions; do
        # Avoid removing the properties array reference!
        [[ $var == "properties" ]] && continue

        : Remove the "this is a required argument" indicator
        var=${var%+}

        #=================================================================
        # If this class' processor requires a resource name, but the
        # user failed to provide one, be sure to send an empty string
        # in its place. If not, then the class processor will blindly
        # take the first argument, and either fail, or product incorrect
        # results.
        #=================================================================
        if [[ $var == "RESOURCE_NAME" && \
              ${_ENV_ARGS[$var]} == *([[:space:]]) ]]
        then
            _ENV_ARGS[$var]='""'
        fi

        [[ " $processor " != *\ $var?(\+)\ * ]] && continue

        value=${_ENV_ARGS[$var]}
        if [[ $value == *[[:space:]]* ]]; then
            processor=${processor/[[:space:]]${var}?(\+)[[:space:]]/ \"$value\" }
        else
            processor=${processor/[[:space:]]${var}?(\+)[[:space:]]/ $value }
        fi

        # Slow down the loop a fraction, to avoid spiking the CPU
        LANG=C sleep .001
    done

    if [[ $processor == *([[:space:]]) ]]; then
        [[ " $CLASSES " != *\ $class\ * ]] && class=${class#*_}
        print "$SENTINEL COMMAND  ($CLMGR_TRANSACTION_ID):  ${CLMGR_COMMAND/ -T $CLMGR_TRANSACTION_ID}" >>$CLMGR_TMPLOG
        print "$SENTINEL INTERNAL ($CLMGR_TRANSACTION_ID):  NOT FOUND!!" >>$CLMGR_TMPLOG
        cl_dspmsg -s $CLMGR_SET $CLMGR_MSGS 183 '\nERROR: unrecognized %1$s operation attempted: %2$s\n\n' "$CLMGR_PROGNAME" "$CLMGR_PROGNAME $_ACTION $class" 1>&2
        log_return_msg "$RC_INCORRECT_INPUT" "$0()" "$LINENO"
        return $?
    fi

    #================================================================
    # Check for a cry for help from the customer! We accept h,H,*,?
    # to ask for help. We also accept an optional "v", but that is
    # intended for internal use only (because the text it displays
    # is *not* translated), and is not documented.
    #================================================================
    typeset -l lcarg= lcargs=
    for lcarg in $_EVENT_SCRIPT_ARGS; do
        [[ $lcarg == *=* ]] && continue
        [[ $lcarg != -* && $lcarg != @(\*|\?) ]] && continue
        lcarg=${lcarg##*-}  # Strip off any leading dashes
        lcargs="$lcargs $lcarg"

        # Slow down the loop a fraction, to avoid spiking the CPU
        LANG=C sleep .001
    done
    typeset -i CLMGR_HELP=0
    for lcarg in $lcargs; do
        [[ $lcarg == *@(h|\*|\?)* && $_ACTION != "runcmd" ]] && CLMGR_HELP=1
        [[ $lcarg == *v*                                  ]] && CLMGR_VERBOSE=1

        # Slow down the loop a fraction, to avoid spiking the CPU
        LANG=C sleep .001
    done
    if (( CLMGR_HELP )); then
        typeset file=${processor%%[[:space:]]*}
        if [[ $file != KLIB_HACMP_* && -f $HAEVENTS/resource_${_ACTION} ]]
        then
            typeset -i found=0
            while read LINE; do
                if (( ! found )); then
                   if [[ $LINE == function+([[:space:]])${file}*([[:space:]])* ]]; then
                        found=1
                   fi
                else
                    if [[ $LINE == *([[:space:]])KLIB_HACMP_* ]]; then
                        file=${LINE#+([[:space:]])}
                        file=${file%%[[:space:]]*}
                        break
                    fi
                fi

                # Slow down the loop a fraction, to avoid spiking the CPU
                LANG=C sleep .001
            done < $HAEVENTS/resource_${_ACTION}
        fi

        typeset POD=
        if (( CLMGR_VERBOSE )); then
            POD=$(which perldoc 2>&1)
            if [[ -z $POD || ! -f $POD && ! -x $POD ]]; then
                POD=$(which pod2text 2>&1)
            fi
        fi
        if [[ -f /usr/es/lib/ksh93/hacmp/$file && \
              -n $POD && -f $POD && -x $POD ]] && \
           (( CLMGR_VERBOSE ))
        then
            $POD /usr/es/lib/ksh93/hacmp/$file
        fi

        typeset action=$_ACTION
        [[ $action == @(get|list) ]] && action="query"

        typeset -u TYPE=${_CLASS_PROCESSORS[$class]}
        TYPE=${TYPE%%[[:space:]]*}
        TYPE=${TYPE#*_}
        typeset -l TYPE_LC=$TYPE
        if [[ " $CLASSES " != *\ $TYPE_LC\ * ]]; then
            TYPE=${TYPE#*_}
            TYPE_LC=$TYPE
            [[ " $CLASSES " != *\ $TYPE_LC\ * ]] && TYPE=${TYPE#*_}
            TYPE_LC=$TYPE
        fi

        case $TYPE in
            APPSERVER)      TYPE="APPLICATION_CONTROLLER" ;;
            APPMONITOR)     TYPE="APPLICATION_MONITOR"    ;;
            RESOURCEGROUP)  TYPE="RESOURCE_GROUP"         ;;
            FILECOLLECTION) TYPE="FILE_COLLECTION"        ;;
        esac
        TYPE_LC=$TYPE

        [[ -n $_SUB_ACTION ]] && TYPE_LC="$TYPE_LC $_SUB_ACTION"

        cl_dspmsg -s $CLMGR_SET $CLMGR_MSGS 4 '# Available options for "clvt %1$s %2$s":\n' "$action" "$TYPE_LC" "$CLMGR_PROGNAME"

        typeset ACTIONS_EXPR="${ACTIONS// /\|}|stop|start"
        if [[ $action == "manage" ]]; then
            ACTIONS_EXPR="$ACTIONS_EXPR|collect|discover|reset|restore|resume|unlock|undo|security|hmc|roha|suspend|nova|cloudroha"
        fi

        for VAR in ${_CLASS_PROCESSORS[$class]}; do
            [[ $VAR == "properties" ]] && continue  # Internal use only!

            : Only display REPOSITORY if it is appropriate
            if [[ $VAR == *REPOS* && $_CLASS == @(cluster|site) ]]
            then
                if [[ $CLUSTER_TYPE == "LC" && $_CLASS == "cluster" ]] || \
                   [[ $CLUSTER_TYPE == *SC  && $_CLASS == "site"    ]]
                then
                    continue
                fi
            fi

            if [[ $VAR == *@($ACTIONS_EXPR)_* ]]; then
                if [[ -z $TYPE ]]; then
                    TYPE=${VAR#*@($ACTIONS_EXPR)_}
                    if [[ $TYPE == APP@(MONITOR|SERVER) ]]; then
                        TYPE=${TYPE/APP/APPLICATION_}
                    fi
                fi

            elif [[ $VAR == RESOURCE_NAME?(\+) ]]; then
                if [[ $action != "collect" ]]; then
                    if [[ $class == "cod" ]]; then
                        VAR=${VAR/RESOURCE/APPLICATION_CONTROLLER}
                    else
                        VAR=${VAR/RESOURCE/$TYPE}
                    fi

                    if [[ $VAR == *\+ ]]; then
                        print "<${VAR%+}>"  # Required argument
                    else
                        print "[ <$VAR> ]"  # Optional argument
                    fi
                fi

            elif [[ $class != *_$VAR ]]; then
                if [[ $VAR != ?(-)@(h|H|\*|\?) ]]; then
                    [[ $VAR != *\+ ]] && VAR="[ $VAR ]"
                    print -- "${VAR%+}"
                fi
            fi

            # Slow down the loop a fraction, to avoid spiking the CPU
            LANG=C sleep .001
        done
        rc=0

    else  # Run the processor
        print "$SENTINEL COMMAND  ($CLMGR_TRANSACTION_ID):  ${CLMGR_COMMAND/ -T $CLMGR_TRANSACTION_ID}" >>$CLMGR_TMPLOG
        print "$SENTINEL INTERNAL ($CLMGR_TRANSACTION_ID):  $processor" >>$CLMGR_TMPLOG

        : Running "$class" processor:  $processor $_NAME_VALUE_PAIRS
        eval CL=$LINENO ${processor//\;/\\\;} ${_NAME_VALUE_PAIRS//\;/\\\;}
        rc=$?
        if (( $rc == 127 )); then
            [[ " $CLASSES " != *\ $class\ * ]] && class=${class#*_}
            cl_dspmsg -s $CLMGR_SET $CLMGR_MSGS 183 '\nERROR: unrecognized %1$s operation attempted: %2$s\n\n' "$CLMGR_PROGNAME" "$CLMGR_PROGNAME $_ACTION $class" 1>&2
            rc=$RC_INCORRECT_INPUT
        fi
        print "$SENTINEL RETURN   ($CLMGR_TRANSACTION_ID):  $rc" >>$CLMGR_TMPLOG
    fi

    log_return_msg "$rc" "$0()" "$LINENO"
    return $?
} # End of "runClassProcessor()"


###############################################################################
#
# Name:        is_enterprise
#
# Description: determines whether or not the locally installed SystemMirror
#              software is Enterprise Edition.
#
# Inputs:      None.
#
# Outputs:     None.
#
# Returns:     0 if Enterprise edition, or 1 if not.
#
###############################################################################
function is_enterprise {
    . $HALIBROOT/log_entry "$0()" "$CL" max

    typeset -i ENTERPRISE=$RC_UNKNOWN

    CL=$LINENO isEnterprise
    (( $? == 1 )) && ENTERPRISE=$RC_SUCCESS || ENTERPRISE=$RC_ERROR

    log_return_msg "$ENTERPRISE" "$0()" "$LINENO"
    return $?
}  # End of "is_enterprise()"


#=============================================================================
# Name:        expand_keyword
#
# Description: Parses the provided list of known, valid attributes for the
#              current resource class, looking for a match for the specified
#              keyword. A "match" is defined as an attribute that begins with
#              the keyword, irrespective of case. The *entire* list will be
#              searched, even if a match is found early on. This is to ensure
#              that there is exactly *one* match, and that the user has not
#              provided an ambiguous entry.
#
# Inputs:      key        The keyword that needs to be expanded.
#              list       The list of known keywords for the current class.
#              expanded   The expanded word. Optional.
#
# Outputs:     If the keyword is found, and expands normally (or needs no
#              expansion at all), then there will be no output at all.
#              If the keyword is not found, or expands to more than one
#              known attribute, then an appropriate error message will be
#              written to STDERR.
#
# Returns:     0 if the keyword is found, and expands to a single match,
#                or is an exact match.
#              1 if the keyword is not found.
#              2 if the keyword is ambiguous, matching more than one attr.
#=============================================================================
function expand_keyword {
    . $HALIBROOT/log_entry "$0()" "$CL" max

    typeset key=$1
    typeset -n list=$2
    if [[ -n $3 ]]; then
        typeset -n expanded=$3
    fi

    typeset -u attr_uc= key_uc=$key
    typeset match=
    typeset -i matches=0 rc=$RC_ERROR

    #=====================================================================
    # To preserve backwards compatibility with previous versions of clvt
    # establish a keyword compatibility map. Each key is the old way of
    # specifying an attribute, and each value is the new attribute name.
    # All this is necessary to avoid breaking migration (what happens if
    # the customer migrations the base product and clvt, but *not* their
    # existing Smart Assists?). Of course, perhaps even more important,
    # and certainly more likely to occur, is avoiding breaking any pre-
    # existing customer scripts! An angry customer is a scary customer!!
    #=====================================================================
    if [[ -n ${COMPAT[$key_uc]} ]]; then
        #===============================================================
        # clmgr wants to treat "DISK" and "PHYSICAL_VOLUME" as one and
        # the same. However, one exception is for the "modify rg" cmd,
        # where "DISK" represents a raw disk (no LVM involvement). So
        # "DISK" needs to be accepted as-is for that one operation.
        #===============================================================
        typeset -i CONVERT=1
        if [[ $key_uc  == DISK*(S)      && \
              $_ACTION == @(add|modify) && \
              $RESOURCE_CLASS == @(mirror|resource)_group ]]
        then
            CONVERT=0
        fi

        if (( CONVERT )); then
            _ENV_ARGS[${COMPAT[$key_uc]}]=${_ENV_ARGS[$key]}
            unset _ENV_ARGS[$key]

            key=${COMPAT[$key_uc]}
            key_uc=$key
        fi
    fi

    for attr_uc in $list; do
        attr_uc=${attr_uc%+}
        [[ $attr_uc == @(PROPERTIES|RESOURCE_NAME) ]] && continue
        if [[ $attr_uc == $key_uc ]]; then   # Exact match
            matches=1
            match="$attr_uc"
            break
        elif [[ $attr_uc == $key_uc* ]]; then  # Possible match
            (( matches++ ))
            [[ -n $match ]] && match="$match "
            match="$match$attr_uc"
        fi

        # Slow down the loop a fraction, to avoid spiking the CPU
        LANG=C sleep .001
    done

    if (( matches == 1 )); then
        if [[ $match != $key_uc ]]; then
            expanded=$match

            #==============================================
            # Replace the short keyword with the expanded
            # keyword in the "_ENV_ARGS" data structure.
            #==============================================
            _ENV_ARGS[$match]=${_ENV_ARGS[$key]}
            unset _ENV_ARGS[$key]

            #==============================================
            # Replace the short keyword with the expanded
            # keyword in the "_NAME_VALUE_PAIRS" string.
            #==============================================
            for akey in $_NAME_VALUE_PAIRS; do
                [[ $akey != *=* ]] && continue

                akey=${akey%%=*}
                akey=${akey##*+([[:space:]])}
                typeset -u akey_uc=$akey
                if [[ $akey_uc == $key ]]; then
                    _NAME_VALUE_PAIRS=${_NAME_VALUE_PAIRS//$akey=/$match=}
                    break
                fi

                # Slow down the loop a fraction, to avoid spiking the CPU
                LANG=C sleep .001
            done
        fi
        rc=$RC_SUCCESS

    elif (( matches > 1 )); then
        rc=$RC_INCORRECT_INPUT
        dspmsg -s $CLMGR_SET $CLMGR_MSGS 31 "\nERROR: an ambiguous \"%1\$s\" option was detected: \"%2\$s\"\n       Matching Options:  %3\$s\n" "$_ACTION ${RESOURCE_CLASS#${_ACTION}_}" "$key" "${match//+([[:space:]])/, }" 1>&2
        unset _ENV_ARGS[$key]

    else
        rc=$RC_INCORRECT_INPUT
        if [[ $_ACTION == "list" && ${RESOURCE_CLASS#${_ACTION}_} == query_* ]]; then
            _ACTION="query"
        fi
        class=${RESOURCE_CLASS#${_ACTION}_}
        dspmsg -s $CLMGR_SET $CLMGR_MSGS 32 "\nERROR: an unrecognized \"%1\$s\" option was detected:  %2\$s\n" "$_ACTION $class" "$key" 1>&2
        unset _ENV_ARGS[$key]
    fi

    log_return_msg "$rc" "$0()" "$LINENO"
    return $?
} # End of "expand_keyword()"


#=============================================================================
# Name:        display_simple_help
#
# Description: Displays a simple list of available input options for the
#              current operation. An attempt is made to indicate if the
#              options are required or optional, based upon information
#              gleaned from the class_processors file. However, that
#              information can't always be perfectly accurate, so caveat
#              emptor.
#
# Inputs:      None of the inputs for this function are explicit. Meaning
#              they are not passed in directly, but rather are global
#              environment variables.
#
#              CLMGR_HELP       A Boolean indicating whether or not
#                               this function is being invoked due
#                               to an explicit request for help. If
#                               not, then it is assumed that an error
#                               has occurred.
#
#              _ACTION          The action (i.e. "add", "modify", ...)
#                               being attempted/queried.
#
#              RESOURCE_CLASS   The class that the action is being run
#                               on (i.e. "cluster", "resource_group", ...).
#
# Outputs:     If this is a help request, the help text is displayed on
#              STDOUT. If it is not a help request, then an error condition
#              is assumed, and the help text is displayed on STDERR.
#
# Returns:     0
#=============================================================================
function display_simple_help {
    typeset -i FH=1
    (( ! CLMGR_HELP )) && FH=2

    typeset action=$_ACTION
    [[ $action == @(get|list) ]] && action="query"

    if [[ -z ${!_COLON_ATTR_ORDER[*]} ]]; then
        print "$0()[$LINENO]($SECONDS): Loading: . $HAEVENTS/resource_common $RESOURCE_CLASS" >>$CLMGR_TMPLOG
        . $HAEVENTS/resource_common $RESOURCE_CLASS
    fi

    typeset class=$RESOURCE_CLASS
    typeset -u TYPE=$class
    typeset -l TYPE_LC=$TYPE
    if [[ " $CLASSES " != *\ $TYPE_LC\ * ]]; then
        TYPE=${TYPE#*_}
        TYPE_LC=$TYPE
        if [[ " $CLASSES " != *\ $TYPE_LC\ * ]]; then
            TYPE=${TYPE#*_}
            TYPE_LC=$TYPE
            if [[ " $CLASSES " != *\ $TYPE_LC\ * ]]; then
                TYPE=${TYPE#*_}
                TYPE_LC=$TYPE
            fi
        fi
    fi

    case $TYPE in
        APPSERVER)      TYPE="APPLICATION_CONTROLLER" ;;
        APPMONITOR)     TYPE="APPLICATION_MONITOR"    ;;
        RESOURCEGROUP)  TYPE="RESOURCE_GROUP"         ;;
        FILECOLLECTION) TYPE="FILE_COLLECTION"        ;;
    esac
    print "$0()[$LINENO]($SECONDS): TYPE == $TYPE" >>$CLMGR_TMPLOG
    TYPE_LC=$TYPE

    [[ -n $_SUB_ACTION ]] && TYPE_LC="$TYPE_LC $_SUB_ACTION"
    (( ! CLMGR_HELP )) && print -u2
    if (( CLMGR_HELP )); then
        cl_dspmsg -s $CLMGR_SET $CLMGR_MSGS 4 '# Available options for "clvt %1$s %2$s":\n' "$action" "$TYPE_LC" "$CLMGR_PROGNAME"
        print ""
    else
        cl_dspmsg -s $CLMGR_SET $CLMGR_MSGS 4 '# Available options for "clvt %1$s %2$s":\n' "$action" "$TYPE_LC" "$CLMGR_PROGNAME" 1>&2
        print -u2 ""
    fi

    typeset attr_list="${_CLASS_PROCESSORS[$class]}"
    attr_list=${attr_list#*[[:space:]]} # Remove script/func name
    if [[ $_ACTION == "manage" ]]; then
        attr_list=${_CLASS_PROCESSORS[${_SUB_ACTION}_$class]}
    fi

    TYPE=$class  # Restore the original class, in caps
    case "$TYPE" in
        QUERY_*) TYPE=${TYPE#QUERY_}
                 if [[ -n ${!_ATTR_ORDER[${class#query_}]} ]] && \
                      ! [[ ${!_ATTR_ORDER[${class#query_}]} == *null* || ${!_ATTR_ORDER[@]} != *${class#query_}* ]]
                 then
                     attr_list="${_ATTR_ORDER[${class#query_}]}"
                 else
                     attr_list="${_COLON_ATTR_ORDER[${class#query_}]}"
                 fi

        ;;
        LIST_*)  TYPE=${TYPE#LIST_} ;;
    esac

    typeset ACTIONS_EXPR="${ACTIONS// /\|}|stop|start"
    if [[ $action == "manage" ]]; then
        ACTIONS_EXPR="$ACTIONS_EXPR|collect|discover|reset|restore|resume|unlock|undo|security|hmc|roha|suspend|nova|cloudroha"
    fi

    for VAR in $attr_list; do
        [[ $VAR == "properties" ]] && continue  # Internal use only!

        : Only display REPOSITORY if it is appropriate
        if [[ $VAR == *REPOS* && $_CLASS == @(cluster|site) ]]
        then
            if [[ $CLUSTER_TYPE == "LC" && $_CLASS == "cluster" ]] || \
               [[ $CLUSTER_TYPE == *SC  && $_CLASS == "site"    ]]
            then
                continue
            fi
        fi

        if [[ $VAR == *@($ACTIONS_EXPR)_* ]]; then
            if [[ -z $TYPE ]]; then
                TYPE=${VAR#*@($ACTIONS_EXPR)_}
                if [[ $TYPE == APP@(MONITOR|SERVER) ]]; then
                    TYPE=${TYPE/APP/APPLICATION_}
                fi
            fi

        elif [[ $VAR == RESOURCE_NAME*(\+) ]]; then
            if [[ $class == "cod" ]]; then
                VAR=${VAR/RESOURCE/APPLICATION_CONTROLLER}
            else
                VAR=${VAR/RESOURCE/$TYPE}
            fi
            if [[ $VAR == *\+ ]]; then
                # Ending with a "+" means this is a required argument
                print -u$FH "<${VAR%+}>"
            else
                # Ending without a "+" means this is an optional argument
                print -u$FH "[ <$VAR> ]"
            fi
        else

            if [[ $VAR != ?(-)@(h|H|\*|\?) ]]; then
                [[ $VAR != *\+ ]] && VAR="[ $VAR ]"
                print -u$FH -- "${VAR%+}"
            fi
        fi

        # Slow down the loop a fraction, to avoid spiking the CPU
        LANG=C sleep .001
    done
    (( ! CLMGR_HELP )) && print -u2

    print "$0()[$LINENO]($SECONDS): Final return code: 0" >>$CLMGR_TMPLOG
    return 0
} # End of "display_simple_help()"


#=============================================================================
# Name:        display_operation_syntax
#
# Description: Attempts to display true syntactical help for the current
#              operation, if the appropriate FPATH function file can be
#              identified, and successfully parsed.
#
# Inputs:      Most of the inputs for this function are implicit. Meaning
#              they are not passed in directly, but rather are global
#              environment variables. There is only one explicit input,
#              "BAD_OPTS".
#
#              BAD_OPTS   A Boolean value indicating whether or
#                         not this function is being invoked due
#                         to an explicit request for help, or in
#                         response to a detection of bad input.
#
#              _ACTION    The action (i.e. "add", "modify", ...)
#                         being attempted/queried.
#
#              class      The class that the action is being run
#                         on (i.e. "cluster", "resource_group", ...).
#
# Outputs:     If this is a help request, the help text is displayed on
#              STDOUT. If it is not a help request, then an error condition
#              is assumed, and the help text is displayed on STDERR.
#
# Returns:     0 if the sytnax could be found and displayed.
#              2 if the syntax was not displayed for some reason.
#=============================================================================
function display_operation_syntax {
    typeset -i BAD_OPTS=$1

    typeset -i rc=$RC_UNKNOWN
    typeset FH=1
    (( BAD_OPTS )) && FH=2

    typeset ORIG_CLASS=$class
    convert_for_filename
    ORIG_CLASS=${ORIG_CLASS#query_}

    typeset action=$_ACTION
    [[ $action == @(get|list) ]] && action="query"

    if [[ $_ACTION == @(sync|verify) && $ORIG_CLASS == *cluster ]]
    then
        class="cluster"
        ORIG_CLASS=$class

    elif [[ $_ACTION == @(modify|sync) && $ORIG_CLASS == *file_collection ]]
    then
        class="file_collection"
        ORIG_CLASS=$class
    fi

    if [[ $SRC_FILE == *filecollection ]]; then
        SRC_FILE=${SRC_FILE/filecollection/file_collection}
    elif [[ $SRC_FILE == *sync_sync_file_collection ]]; then
        SRC_FILE=${SRC_FILE/sync_sync/sync}
    fi

    if [[ ! -f $SRC_FILE ]]; then
        SRC_FILE=$(print -- "$SRC_FILE" | sed 's/\(.*\)_/\1/')
    fi
    print "$0()[$LINENO]($SECONDS): SRC_FILE == $SRC_FILE" >>$CLMGR_TMPLOG

    if [[ -f $SRC_FILE ]]; then
        rc=$RC_SUCCESS

        typeset LINE="" SYNTAX=""

        if [[ $_ACTION == @(sync|verify) && $class == *cluster ]]
        then
            typeset -u ACTION_UC=$_ACTION
            print "$0()[$LINENO]($SECONDS): podselect -section SYNOPSIS_$ACTION_UC $SRC_FILE | sed -e '1d'" >>$CLMGR_TMPLOG
            SYNTAX=$(podselect -section SYNOPSIS_$ACTION_UC $SRC_FILE | sed -e '1d')
            print "$0()[$LINENO]($SECONDS): SYNOPSIS_$ACTION_UC length: ${#SYNTAX}" >>$CLMGR_TMPLOG
        else
            print "$0()[$LINENO]($SECONDS): podselect -section SYNOPSIS $SRC_FILE | sed -e '1d'" >>$CLMGR_TMPLOG
            SYNTAX=$(podselect -section SYNOPSIS $SRC_FILE | sed -e '1d')
            print "$0()[$LINENO]($SECONDS): SYNOPSIS length: ${#SYNTAX}" >>$CLMGR_TMPLOG
        fi

        if [[ -n $SYNTAX ]]; then
            #
            : For the "add/modify cluster" syntax, there are values required
            : that vary based on the level of AIX. So make an attempt to look
            : them up here, and if found, replace the textual placeholders in
            : the syntax message with the actual values.
            #
            if [[ $class == "cluster" && $_ACTION == @(add|modify) ]]
            then
                typeset GP_MIN="" GP_MAX="" JUNK=""
                clctrl -tune -L node_down_delay 2>>$CLMGR_TMPLOG | grep -w node_down_delay | read JUNK JUNK GP_MIN GP_MAX JUNK
                if [[ $GP_MIN == +([0-9]) && $GP_MAX == +([0-9]) ]]
                then
                    (( GP_MIN /= 1000 ))
                    (( GP_MAX /= 1000 ))
                    SYNTAX=${SYNTAX// GRACE_PERIOD=\<+([[:alnum:]])+(\.)+([[:alnum:]])\>/ GRACE_PERIOD=\<$GP_MIN..$GP_MAX\>}
                fi
                 
                typeset HF_MIN="" HF_MAX=""
                clctrl -tune -L node_timeout 2>>$CLMGR_TMPLOG | grep -w node_timeout | read JUNK JUNK HF_MIN HF_MAX JUNK
                if [[ $HF_MIN == +([0-9]) && $HF_MAX == +([0-9]) ]]
                     then
                         (( HF_MIN /= 1000 ))
                         (( HF_MAX /= 1000 ))
                         SYNTAX=${SYNTAX//HEARTBEAT_FREQUENCY=\<+([[:alnum:]])+(\.)+([[:alnum:]])\>/HEARTBEAT_FREQUENCY=\<$HF_MIN..$HF_MAX\>}
                     fi
                 fi

            typeset FIRST_WORD=""
            print -- "$SYNTAX" |\
            while IFS="" read -r LINE; do
                #=============================================================
                # Do not display non-syntax lines. A syntax line must adhere
                # to the following "rules":
                #
                #   * ends with a "\", "]", "}", or "|" (trailing spaces
                #     are ignored)
                #   * contains a list and continuation char:  a|b|c \
                #   * contains a VAR=VAL pair
                #=============================================================
                if [[ $LINE != *[\\\]\}\|]*([[:space:]]) && \
                      $LINE != *+([a-zA-Z0-9_])\|*(*([[:space:]])\\)*([[:space:]]) && \
                      $LINE != +([[:space:]])+([a-zA-Z0-9_])=* && \
                      $LINE != *([[:space:]])clmgr\ * ]]
                then
                    continue
                fi

                if [[ $FIRST_WORD == *([[:space:]]) ]]; then
                    FIRST_WORD=${LINE##+([[:space:]])}
                    FIRST_WORD=${FIRST_WORD%%+([[:space:]])*}
                fi

                if [[ $LINE == *([[:space:]])$FIRST_WORD\ * ]]; then
                    print -u$FH ""
                    if [[ $FIRST_WORD != "clmgr" && \
                          " $ACTIONS " == *\ $FIRST_WORD\ * ]]
                    then
                        LINE=${LINE/$FIRST_WORD/clmgr $FIRST_WORD}
                    fi
                fi
                [[ $LINE != \ * ]] && LINE=" $LINE"
                print -u$FH -- "$LINE"
            done

            if (( ! BAD_OPTS )); then
                typeset -A _ALIAS_MAP
                _ALIAS_MAP=(
                    [add]="build, bld, create, make, mk"
                    [compare]="co, dif"
                    [discover]="dis"
                    [delete]="erase, remove, rm"
                    [list]="ls"
                    [manage]="mg"
                    [modify]="change, set"
                    [move]="mv"
                    [offline]="stop, deactivate"
                    [online]="start, activate"
                    [query]="get, show"
                    [recover]="rc"
                    [replace]="rp, swap, switch"
                    [sync]="propagate"
                    [verify]="validate, test"
                    [view]="cat"

                    [application_controller]="ac, app, appctl"
                    [application_monitor]="am, mon, appmon"
                    [backup_files]="bf, backup_f"
                    [cluster]="cl"
                    [cod]="cuod, dlpar, roha"
                    [dependency]="de"
                    [event]="ev"
                    [fallback_timer]="fa, ft, timer"
                    [file_collection]="fc"
                    [file_system]="fs"
                    [group]="gp"
                    [interface]="in, if"
                    [ldap_client]="lc"
                    [ldap_server]="ls"
                    [logical_volume]="lv"
                    [method]="me"
                    [mirror_group]="mig"
                    [mirror_pair]="mip"
                    [mirror_pool]="mp, pool"
                    [network]="ne, nw"
                    [node]="no"
                    [persistent_ip]="pi"
                    [physical_volume]="pv, disk"
                    [repository]="rp"
                    [resource_group]="rg"
                    [service_ip]="si"
                    [site]="st"
                    [smart_assist]="sm, sa"
                    [snapshot]="sn, ss"
                    [storage_agent]="sta"
                    [storage_system]="sts"
                    [tape]="tp"
                    [user]="ur"
                    [volume_group]="vg"
                    [backup_profile]="bp, backup_p, replication_profile"
                )

                if [[ -n ${_ALIAS_MAP[$action]} ]]; then
                    print -u$FH "$NL $action => ${_ALIAS_MAP[$action]}"
                    if [[ -n ${_ALIAS_MAP[$ORIG_CLASS]} ]]; then
                        [[ $class == "changes" ]] && class=$ORIG_CLASS
                        print -u$FH " $ORIG_CLASS => ${_ALIAS_MAP[$ORIG_CLASS]}$NL"
                    else
                        print -u$FH ""
                    fi
                elif [[ -n ${_ALIAS_MAP[$ORIG_CLASS]} ]]; then
                    print -u$FH "$NL $class => ${_ALIAS_MAP[$ORIG_CLASS]}$NL"
                else
                    print -u$FH ""
                fi

                unset _ALIAS_MAP
            else
                print -u$FH ""
            fi

        else
            rc=$RC_NOT_FOUND
        fi

    else
        rc=$RC_NOT_FOUND
    fi

    print "$0()[$LINENO]($SECONDS): Final return code: $rc" >>$CLMGR_TMPLOG
    return $rc
} # End of "display_operation_syntax()"


#=============================================================================
# Name:        display_operation_man_page
#
# Description: Attempts to display the section of the man page that is
#              relevant for the current operation, if the appropriate
#              information can be found in the man page.
#
# Inputs:      None of the inputs for this function are explicit. Meaning
#              they are not passed in directly, but rather are global
#              environment variables.
#
#              _ACTION    The action (i.e. "add", "modify", ...)
#                         being attempted/queried.
#
#              class      The class that the action is being run
#                         on (i.e. "cluster", "resource_group", ...).
#
# Outputs:     If successful, opens the clmgr man page into a pager,
#              either "less" or "more", and jumps to the appropriate
#              section of the document for the current operation.
#
# Returns:     0 if the man page was found, and successfully loaded/displayed.
#              2 if the man page was not displayed for some reason.
#=============================================================================
function display_operation_man_page {
    typeset -i rc=$RC_UNKNOWN

    typeset MANFILE="/usr/share/man/info/EN_US/a_doc_lib/cmds/powerha_cmds/clmgr.htm"
    if [[ -f $MANFILE ]]; then
        typeset PATTERN=""
        export MANPATH="/usr/share/man:/usr/man:$MANPATH"

        # Validate any user-specified pager
        if [[ $PAGER != *([[:space:]]) ]]; then
            print "$0()[$LINENO]($SECONDS): User-specified PAGER is \"$PAGER\"." >>$CLMGR_TMPLOG
            if [[ $PAGER == */* && ! -x $PAGER ]]; then
                print "$0()[$LINENO]($SECONDS): ERROR: the current PAGER value, \"$PAGER\", is invalid." >>$CLMGR_TMPLOG
                print "$0()[$LINENO]($SECONDS):        \"$PAGER\" is either not executable, or does not exist at all." >>$CLMGR_TMPLOG
                print "$0()[$LINENO]($SECONDS):        An attempt will be made to find an alternate pager." >>$CLMGR_TMPLOG
                PAGER=""
            elif ! type -a ${PAGER%% *} >/dev/null 2>&1
            then
                print "$0()[$LINENO]($SECONDS): ERROR: the current PAGER value, \"$PAGER\", is invalid." >>$CLMGR_TMPLOG
                print "$0()[$LINENO]($SECONDS):        \"$PAGER\" could not be found." >>$CLMGR_TMPLOG
                print "$0()[$LINENO]($SECONDS):        An attempt will be made to find an alternate pager." >>$CLMGR_TMPLOG
                PAGER=""
            fi
        fi

        # Select a pager based from worst to best
        if [[ $PAGER == *([[:space:]]) ]]; then
            PAGER=/usr/bin/pg
            [[ -f /usr/bin/more ]] && PAGER="/usr/bin/more"
            [[ -f /usr/bin/less ]] && PAGER="/usr/bin/less"
        fi

        if [[ $PAGER == *?(/)@(more|less) ]]; then
            PAGER="${PAGER%% *} -p"
        fi

        # Perform any necessary class conversion, to
        # fit what exists in the man page information.
        case $class in
            query_*) _ACTION=${class%%_*}
                     class=${class#*_}
            ;;
        esac

        integer FOUND=0
        for PATTERN in "clmgr $_ACTION ${class}s" \
                       "clmgr $_ACTION $class"
        do
            typeset LINE=""
            if [[ -n $_SUB_ACTION ]]; then
                LINE=$(egrep "$PATTERN" $MANFILE |\
                       egrep $_SUB_ACTION |\
                       egrep "^[[:space:]]+clmgr ")
            else
                LINE=$(egrep "$PATTERN" $MANFILE)
            fi
            LINE=${LINE%%${NL}*}
            if [[ -n $LINE ]]; then
                (( FOUND++ ))

                if [[ $PAGER == *pg ]]; then
                    PATTERN="+/$PATTERN/"
                elif [[ $PAGER == *more* ]]; then
                    PATTERN="/$PATTERN"
                fi

                print "$0()[$LINENO]($SECONDS): man clmgr | $PAGER \"$PATTERN\"" >>$CLMGR_TMPLOG
                man clmgr | $PAGER "$PATTERN"
                rc=$?
                print "$0()[$LINENO]($SECONDS): $PAGER RC: $rc" >>$CLMGR_TMPLOG

                break
            fi
        done

        if (( ! FOUND )); then
            print "$0()[$LINENO]($SECONDS): man clmgr | ${PAGER%% *}" >>$CLMGR_TMPLOG
            man clmgr | ${PAGER%% *}
            rc=$?
            print "$0()[$LINENO]($SECONDS): ${PAGER%% *} RC: $rc" >>$CLMGR_TMPLOG

            (( rc != RC_SUCCESS )) && rc=$RC_NOT_FOUND
        fi

    else
        rc=$RC_NOT_FOUND
    fi

    print "$0()[$LINENO]($SECONDS): Final return code: $rc" >>$CLMGR_TMPLOG
    return $rc
} # End of "display_operation_man_page()"


#=============================================================================
# Name:        convert_for_filename
#
# Description: A helper function that is used to try to help identify
#              the matching source file for the current operation.
#
# Inputs:      None of the inputs for this function are explicit. Meaning
#              they are not passed in directly, but rather are global
#              environment variables.
#
#              _ACTION       The action (i.e. "add", "modify", ...)
#                            being attempted/queried.
#
#              _SUB_ACTION   A secondary action, used in conjunction
#                            with the "manage" action.
#
#              class         The class that the action is being run
#                            on (i.e. "cluster", "resource_group", ...).
#
# Outputs:     Changes the "class" environment variable, if needed.
#              Sets the SRC_FILE environment variable to the absolute
#              path of the FPATH function file for this operation.
#
# Returns:     0 if the source file is found. Otherwise, 2.
#=============================================================================
function convert_for_filename {
    if [[ $_ACTION == "manage" ]]; then
        case $_SUB_ACTION in
            collect) class="${class}s"      ;;
            resume)  class="app_monitoring" ;;
            suspend) class="app_monitoring" ;;
            discover)
                [[ $class == "cluster" ]] && _SUB_ACTION="run" && class="discovery"
            ;;
            rehearsals)
                [[ $class == "cluster" ]] && _SUB_ACTION="cluster" && class="rehearsals"
            ;;
            security) _SUB_ACTION="modify" && class="cluster_security"
            ;;
            hmc) _SUB_ACTION="modify" && class="cluster_hmc"
            ;;
            roha) _SUB_ACTION="modify" && class="cluster_roha"
            ;;
            nova) _SUB_ACTION="modify" && class="cluster_nova"
            ;;
            cloudroha) _SUB_ACTION="modify" && class="cluster_cloudroha"
            ;;
            undo_changes)
                [[ $class == "node" ]] && _SUB_ACTION="undo_cfg" && class="changes"
            ;;
        esac
        SRC_FILE=$HALIBROOT/hacmp/KLIB_HACMP_${_SUB_ACTION}_${class}

    elif [[ $_ACTION == @(sync|verify) && $class == *cluster ]]; then
        : KLIB_HACMP_verify_and_sync handles verification and synchronization
        SRC_FILE=$HALIBROOT/hacmp/KLIB_HACMP_verify_and_sync

    elif [[ $_ACTION == "discover" ]]; then
        : KLIB_HACMP_run_discovery handles all discovery
        SRC_FILE=$HALIBROOT/hacmp/KLIB_HACMP_run_discovery

    else
        class=${class#query_}
        case $class in
            file_collection)        class="filecollection" ;;
            application_controller) class="appserver"      ;;
            application_monitor)    class="appmonitor"     ;;
        esac
        case $_ACTION in
            query) if [[ $class == "backup_files" ]]
                   then
                       _ACTION="list"
                   # As there is no file called KLIB_HACMP_get_smart_assist making SRC_FILE name as KLIB_HACMP_list_smart_assists
                   # while querying for smart assists
                   elif [[ $class == "smart_assist" ]]
                   then
                       _ACTION="list"
                       class="${class}s"
                   else
                       [[ $class == "resource_group" ]] && class="rg"
                       _ACTION="get" && class="${class}_attributes"
                   fi
                   ;;
        esac
        SRC_FILE=$HALIBROOT/hacmp/KLIB_HACMP_${_ACTION}_${class}
    fi

    typeset -i rc=$RC_SUCCESS
    [[ -z $SRC_FILE || ! -f $SRC_FILE ]] && rc=$RC_NOT_FOUND
    return $rc
} # End of "convert_for_filename()"



#########################################################################
#########################################################################
##
##  MAIN Main main
##
#########################################################################
#########################################################################

export HALIBROOT=/usr/es/lib/ksh93
. $HALIBROOT/log_entry resource_common "$CL" max

#=============================================================
: Attempt to avoid redundant loads of the func_include file.
: It is large, and runs some operations upon load that make
: redundant loads an unwelcome performance hit.
#=============================================================
type prune_indexes >/dev/null 2>&1
if (( $? != 0 ))
then
    CL=$LINENO . $HALIBROOT/func_include
fi

export CLMGR_MSGS=command.cat
export CLVT_MSG=$CLMGR_MSGS
export CLMGR_SET=2
export CLVT_SET=$CLMGR_SET

typeset key
typeset -u upper_key
typeset -A _ENV_ARGS

#============================================
# Establish the standard clmgr return codes.
# Note these values should match those with
# the same string representation in clmgr.h
#============================================
## A result is not known. Useful as an initializer.
export RC_UNKNOWN=-1
## No errors were detected; the operation appears to have been successful
export RC_SUCCESS=0
## A general error has occurred
export RC_ERROR=1
## A specified resource does not exist, or could not be found
export RC_NOT_FOUND=2
## Some required input was missing
export RC_MISSING_INPUT=3
## Some detected input was incorrect in some way
export RC_INCORRECT_INPUT=4
## A required dependency does not exist
export RC_MISSING_DEPENDENCY=5
## A specified search failed to match any data
export RC_SEARCH_FAILED=6
## A given operation was not really necessary
export RC_EXTRANEOUS=7
## Chosen timeout value is exhausted
export RC_TIMEOUT=8

#==============================================================================
# These lists, _COLON_ATTR_ORDER and _ATTR_ORDER, are used to specify the
# ordering of the displayed attributes. The only difference between the
# two is that _COLON_ATTR_ORDER is intended for colon-delimited output,
# in which order is critical, and may NEVER BE CHANGED. New attributes
# *must* be appended, not inserted!! The _ATTR_ORDER array is meant for
# use where we badly wanted to change the output order... just not for
# colon-delimited output (can't do it there!) So if a class needs a new
# attribute, for example, but it logically fits in the middle of the
# display of the existing attributes for that class, do this:
#
#    1. Append the new attribute to the end of the attributes list
#       in the _COLON_ATTR_ORDER array.
#    2. Copy that line from _COLON_ATTR_ORDER and insert it into
#       the _ATTR_ORDER, defined below. Then in that array only,
#       move the new attribute to wherever you need it to be.
#    3. Test the output in normal and XML format, confirming
#       the new attribute is in the desired position.
#    4. Test the output in colon format, confirming that the new
#       attribute appears at the very end of the output.
#
# Only the attributes listed in these arrays will be displayed, even if
# additional attributes are retrieved in the "getter" functions (those
# will be ignored, even though present). All attributes *must* be defined
# in _COLON_ATTR_ORDER, but _ATTR_ORDER is only necessary if the attribute
# output sequence needs to be changed.
#
# NOTE: if any attributes have a numeric value as the last character(s), such
#       as the case with "EXPORT_FILESYSTEM_V4", then some code will need to
#       be added to "SerializeAsAssociativeArray()" and "search_properties()"
#       to properly handle them. Without this, the attributes are unlikely to
#       be displayed.
#==============================================================================
_COLON_ATTR_ORDER=(
    [cluster]="CLUSTER_NAME CLUSTER_ID STATE VERSION VERSION_NUMBER EDITION CLUSTER_IP UNSYNCED_CHANGES FC_SYNC_INTERVAL RG_SETTLING_TIME RG_DIST_POLICY MAX_EVENT_TIME MAX_RG_PROCESSING_TIME DAILY_VERIFICATION VERIFICATION_NODE VERIFICATION_HOUR VERIFICATION_DEBUGGING LEVEL ALGORITHM MECHANISM CERTIFICATE PRIVATE_KEY HEARTBEAT_FREQUENCY GRACE_PERIOD SITE_POLICY_FAILURE_ACTION SITE_POLICY_NOTIFY_METHOD SITE_HEARTBEAT_CYCLE SITE_GRACE_PERIOD SPLIT_POLICY MERGE_POLICY NFS_QUORUM_SERVER LOCAL_QUORUM_DIRECTORY REMOTE_QUORUM_DIRECTORY QUARANTINE_POLICY CRITICAL_RG ACTION_PLAN TIEBREAKER NOTIFY_METHOD NOTIFY_INTERVAL MAXIMUM_NOTIFICATIONS DEFAULT_SURVIVING_SITE APPLY_TO_PPRC_TAKEOVER HEARTBEAT_TYPE RESPONSE_NEEDED TEMP_HOSTNAME REPOSITORIES TYPE DEFAULT_HMC_TIMEOUT DEFAULT_HMC_RETRY_COUNT DEFAULT_HMC_RETRY_DELAY DEFAULT_HMCS_LIST ALWAYS_START_RG ADJUST_SPP_SIZE FORCE_SYNC_RELEASE AGREE_TO_COD_COSTS ONOFF_DAYS LPM_POLICY HEARTBEAT_FREQUENCY_DURING_LPM NETWORK_FAILURE_DETECTION_TIME AUTOMATIC_REPOSITORY_REPLACEMENT RESOURCE_ALLOCATION_ORDER HMC_CONNECTION_TYPE CAA_AUTO_START_DR DEFAULT_NOVA_TIMEOUT DEFAULT_NOVA_RETRY_COUNT DEFAULT_NOVA_RETRY_DELAY NOVA_CONNECTION_TYPE CAA_REPOS_MODE CAA_CONFIG_TIMEOUT LVM_PREFERRED_READ CRIT_DAEMON_RESTART_GRACE_PERIOD SKIP_EVENT_PROCESSING_MANAGE_MODE BUCKET_NAME CLOUD_SERVICE USE_EXISTING_BUCKET ONPREM_HYBRID_CLOUD"
    [repository]="NAME NODE SITE PVID UUID BACKUP TYPE DESCRIPTION SIZE AVAILABLE CONCURRENT ENHANCED_CONCURRENT_MODE STATUS"
    [site]="NAME GID STATE NODES SITE_IP RECOVERY_PRIORITY DOMINANT REPOSITORIES HMCS"
    [node]="NAME HOSTNAME GID STATE RAW_STATE COMMPATH VERSION VERSION_NUMBER EDITION AIX_LEVEL UNSYNCED_CHANGES IPLABEL IPADDRESS TYPE NETWORK NETTYPE NETMASK START_ON_BOOT BROADCAST_ON_START CLINFO_ON_START COMMUNICATION CAA_STATE LOCALHOST ENABLE_LIVE_UPDATE HMCS ENABLE_CAA_AFTER_MERGE NOVAS HOST_LABEL PERSISTENT_IPS CRIT_DAEMON_RESTART_GRACE_PERIOD CLOUD_BASED"
    [host]="HOSTNAME IPADDRESS HAVERSION VERSION_NUMBER HAEDITION AIX_LEVEL LOCALHOST"
    [network]="NAME GID NETMASK NET_FAMILY NETWORK_ID ATTR ALIAS RESOURCE_DIST_PREF POLLINTERVAL TYPE GLOBALNAME ALIAS_HB_NETMASK ALIAS_HB_ADDR MONITOR_METHOD PUBLIC INTERFACE INTERFACENAME IPADDR NODE INTERFACETYPE GLOBALNAME HADDR SOURCE_IP UNSTABLE_THRESHOLD UNSTABLE_PERIOD BOOT_ADAPTERS SERVICE_ADAPTERS PERSISTENT_ADAPTERS SINGLE_ADAPTER_NETWORK"
    [interface]="NAME INTERFACE IPADDR NETMASK PREFIX NETWORK NODE NETTYPE TYPE ATTR HWADDR STATE"
    [file_collection]="NAME DESCRIPTION SYNC_WITH_CLUSTER SYNC_WHEN_CHANGED FILES FILE SIZE"
    [log]="NAME DESCRIPTION DIRECTORY DEFAULT TYPE SIZE REMOTE_FS TRACE_LEVEL FORMATTING"
    [snapshot]="NAME DESCRIPTION METHODS CAPTURE_DATE CAPTURE_NODE CLUSTER_NAME CLUSTER_TYPE NODES SITES HOSTS SNAPSHOTPATH PAGER SDIFF_OUTPUT_WIDTH SDIFF_FLAGS MISC_INFO AVAILABLE_SECTIONS"

    [resource_group]="NAME CURRENT_NODE NODES STATE TYPE APPLICATIONS STARTUP FALLOVER FALLBACK NODE_PRIORITY_POLICY DISK VOLUME_GROUP FORCED_VARYON FILESYSTEM FSCHECK_TOOL RECOVERY_METHOD EXPORT_FILESYSTEM SHARED_TAPE_RESOURCES AIX_CONNECTIONS_SERVICES AIX_FAST_CONNECT_SERVICES COMMUNICATION_LINKS MOUNT_FILESYSTEM SERVICE_LABEL MISC_DATA SSA_DISK_FENCING VG_AUTO_IMPORT INACTIVE_TAKEOVER CASCADE_WO_FALLBACK FS_BEFORE_IPADDR NFS_NETWORK MOUNT_ALL_FS WLM_PRIMARY WLM_SECONDARY FALLBACK_AT EXPORT_FILESYSTEM_V4 STABLE_STORAGE_PATH WPAR_NAME VARYON_WITH_MISSING_UPDATES DATA_DIVERGENCE_RECOVERY SECONDARYNODES SECONDARY_STATE RELATIONSHIP SITE_POLICY MIRROR_GROUP NODE_PRIORITY_POLICY_SCRIPT NODE_PRIORITY_POLICY_TIMEOUT CURRENT_SECONDARY_NODE"
    [application_controller]="NAME ASSOCIATEDMONITORS MONITORS STARTSCRIPT STOPSCRIPT CPU_USAGE_MONITOR PROCESS_TO_MONITOR_CPU_USAGE CPU_USAGE_MONITOR_INTERVAL RESOURCE_GROUP MONITORING_STATUS RAW_MONITORING_STATUS STARTUP_MODE"
    [application_monitor]="NAME APPLICATIONS TYPE MODE MONITORMETHOD MONITORINTERVAL PROCESSES OWNER INSTANCECOUNT HUNGSIGNAL STABILIZATION RESTARTCOUNT RESTARTINTERVAL FAILUREACTION NOTIFYMETHOD CLEANUPMETHOD RESTARTMETHOD MONITORRETRYCOUNT AMLOGGING"
    [dependency]="NAME TYPE GROUPS ACQUIRE_SERIALLY ACQUIRE_IN_PARALLEL RELEASE_SERIALLY RELEASE_IN_PARALLEL PARENT CHILD STOP START AFTER HIGH INTERMEDIATE LOW"
    [fallback_timer]="NAME REPEATS YEAR MONTH MONTH_NUM DAY_OF_MONTH DAY_OF_WEEK DAY_OF_WEEK_NUM HOUR MINUTE"
    [file_system]="NAME TYPE VFS NODES VOLUME_GROUP RESOURCE_GROUP DEVICE SIZE OPTIONS AUTOMOUNT DISK_ACCOUNTING BLOCK_SIZE LV_FOR_LOG INLINE_LOG_SIZE EXT_ATTR_FORMAT ENABLE_QUOTA_MGMT EFS MOUNTGUARD FRAGMENT_SIZE BYTES_PER_INODE ALLOC_GROUP_SIZE PERMISSIONS LOGICAL_VOLUME"
    [logical_volume]="NAME NODES VOLUME_GROUP RESOURCE_GROUP MOUNT_POINT LABEL PERMISSION VG_STATE LV_STATE TYPE MAX_LPS COPIES LPS PPS PP_SIZE STALE_PPS WRITE_VERIFY MIRROR_WRITE_CONSISTENCY INTER-POLICY INTRA-POLICY RELOCATABLE UPPER_BOUND SCHED_POLICY BB_POLICY EACH_LP_COPY_ON_A_SEPARATE_PV SERIALIZE_IO IDENTIFIER FIRST_COPY_MIRROR_POOL SECOND_COPY_MIRROR_POOL THIRD_COPY_MIRROR_POOL PHYSICAL_VOLUMES SIZE ENCRYPTION"
    [method]="NAME TYPE DESCRIPTION FILE SOURCE CONTACT EVENTS NODES RETRY TIMEOUT"
    [persistent_ip]="NAME IPADDR NODE NETMASK PREFIX NETWORK INTERFACE NETTYPE TYPE ATTR GLOBALNAME HADDR"
    [physical_volume]="NAME PVID TYPE DESCRIPTION SIZE AVAILABLE CONCURRENT ENHANCED_CONCURRENT_MODE STATUS UUID VOLUME_GROUP SCSIPR_CAPABLE"
    [service_ip]="NAME IPADDR NODE NETMASK PREFIX NETWORK INTERFACE NETTYPE TYPE ATTR GLOBALNAME HWADDR SITE"
    [tape]="NAME DESCRIPTION DEVICE STARTSCRIPT START_SYNCHRONOUSLY STOPSCRIPT STOP_SYNCHRONOUSLY"
    [volume_group]="NAME TYPE NODES LOGICAL_VOLUMES PHYSICAL_VOLUMES MIRROR_POOLS STRICT_MIRROR_POOLS RESOURCE_GROUP AUTO_ACTIVATE QUORUM CONCURRENT_ACCESS CRITICAL FAILUREACTION NOTIFYMETHOD MIGRATE_FAILED_DISKS SYNCHRONIZE LOGICAL_TRACK_GROUP_SIZE MAX_PHYSICAL_PARTITIONS PPART_SIZE MAX_LOGICAL_VOLUMES MAJOR_NUMBER IDENTIFIER TIMESTAMP SCSIPR_CAPABLE LVM_PREFERRED_READ ENCRYPTION"
    [mirror_pool]="NAME VOLUME_GROUP PHYSICAL_VOLUMES STORAGE_LOCATION SUPER_STRICT MODE ASYNC_CACHE_LV ASYNC_CACHE_HW_MARK ASYNC_MIRROR_STATE ASYNC_CACHE_VALID ASYNC_DATA_DIVERGENCE ASYNC_CACHE_EMPTY ASYNC_CACHE_UTILIZATION CACHE_FREE_SPACE"

    [ldap_client]="SERVERS ADMIN_DN SUFFIX PORT SSL_KEY AUTH_TYPE"
    [ldap_server]="SERVERS ADMIN_DN BASE_DN SSL_KEY PORT SCHEMA VERSION"
    [user]="NAME ID ADMINISTRATIVE PRIMARY GROUPS ADMIN_GROUPS ROLES \
            SWITCH_USER SU_GROUPS HOME SHELL INFO EXPIRATION LOCKED LOGIN \
            REMOTE_LOGIN SCHEDULE MAX_FAILED_LOGINS AUTHENTICATION \
            ALLOWED_TTYS DAYS_TO_WARN PASSWORD_VALIDATION_METHODS \
            PASSWORD_FILTERS MIN_PASSWORDS REUSE_TIME LOCKOUT_DELAY \
            MAX_PASSWORD_AGE MIN_PASSWORD_AGE MIN_PASSWORD_LENGTH \
            MIN_PASSWORD_ALPHAS MIN_PASSWORD_OTHERS MAX_PASSWORD_REPEATED_CHARS\
            MIN_PASSWORD_DIFFERENT REGISTRY UMASK AUDIT_CLASSES TRUSTED_PATH \
            PRIMARY_AUTH SECONDARY_AUTH PROJECTS KEYSTORE_ACCESS \
            ADMIN_KEYSTORE_ACCESS KEYSTORE_MODE ALLOW_MODE_CHANGE \
            KEYSTORE_ENCRYPTION FILE_ENCRYPTION"
    [group]="NAME ID USERS ADMINS ADMINISTRATIVE REGISTRY PROJECTS KEYSTORE_MODE \
            KEYSTORE_ENCRYPTION KEYSTORE_ACCESS"
    [efs]="MODE VOLUME_GROUP SERVICE_IP"

    [storage_agent]="NAME TYPE ADDRESSES USER PASSWORD \
            ATTRIBUTES"
    [storage_system]="NAME TYPE VENDOR_ID WWNN SITE \
            AGENTS ATTRIBUTES ROLE ADDRESSES PARTNER USER BACKUP_PROFILE SVC_CLUSTER_ID"
    [mirror_pair]="NAME FIRST_DISK SECOND_DISK"
    [mirror_group]="NAME TYPE RESOURCE_GROUP MODE RECOVERY STORAGE_SYSTEMS \
            CONSISTENT VENDOR_ID ATTRIBUTES MG_TYPE HYPERSWAP_ENABLED \
            HYPERSWAP_PRIORITY UNPLANNED_HS_TIMEOUT VOLUME_GROUPS RAW_DISKS \
            SYS_MG_NODE REPOSITORY_MG_SITES REPOSITORY_MG_HS_DISK \
            REPOSITORY_MG_NONHS_DISK MIRROR_PAIRS HORCM_INSTANCE \
            HORCM_TIMEOUT PAIR_EVENT_TIMEOUT RESYNC STATE"

    [event]="NAME TYPE FILE DESCRIPTION NOTIFY_COMMAND \
            PRE_EVENT_COMMAND POST_EVENT_COMMAND PREPOSTFAILS"
    [hmc]="NAME TIMEOUT RETRY_COUNT RETRY_DELAY NODES SITES \
            STATUS VERSION USER_NAME PASSWORD"
    [cod]="NAME USE_DESIRED OPTIMAL_MEM OPTIMAL_CPU OPTIMAL_PU OPTIMAL_VP"
    [nova]="NAME TIMEOUT RETRY_COUNT RETRY_DELAY NODES STATUS \
            VERSION USER_NAME PASSWORD MANAGED_SYSTEM"
    [backup_profile]="RESOURCE_GROUP ENABLE_BACKUP VOLUME_GROUP REPLICATED_RESOURCES \
                    STORAGE_NAME BUCKET_NAME TARGET_LOCATION \
                    BACKUP_METHOD CLOUD_SERVICE COMPRESSION BACKUP_FREQUENCY \
                    BACKUP_SCHEDULE INC_BACKUP_FREQ NOTIFYMETHOD ENCRYPTION \
                    BACKUP_PROGRESS NEXT_BACKUP_SCHEDULE"
)
_COLON_ATTR_ORDER[filecollection]="${_COLON_ATTR_ORDER[file_collection]}"

#========================================================================
: This structure is not used for colon-delimited output, so the order
: of the attributes may be safely adjusted, as-needed. Make sure all
: attributes added to this list are *also* added to the colon-delimited
: sequence specified in _COLON_ATTR_ORDER, defined above.
#========================================================================
_ATTR_ORDER=(
    [cluster]="CLUSTER_NAME CLUSTER_ID STATE TYPE HEARTBEAT_TYPE CLUSTER_IP REPOSITORIES VERSION VERSION_NUMBER EDITION UNSYNCED_CHANGES FC_SYNC_INTERVAL RG_SETTLING_TIME RG_DIST_POLICY MAX_EVENT_TIME MAX_RG_PROCESSING_TIME TEMP_HOSTNAME DAILY_VERIFICATION VERIFICATION_NODE VERIFICATION_HOUR VERIFICATION_DEBUGGING LEVEL ALGORITHM MECHANISM CERTIFICATE PRIVATE_KEY HEARTBEAT_FREQUENCY GRACE_PERIOD SITE_POLICY_FAILURE_ACTION SITE_POLICY_NOTIFY_METHOD SITE_HEARTBEAT_CYCLE SITE_GRACE_PERIOD SPLIT_POLICY MERGE_POLICY NFS_QUORUM_SERVER LOCAL_QUORUM_DIRECTORY REMOTE_QUORUM_DIRECTORY QUARANTINE_POLICY CRITICAL_RG ACTION_PLAN TIEBREAKER NOTIFY_METHOD NOTIFY_INTERVAL MAXIMUM_NOTIFICATIONS DEFAULT_SURVIVING_SITE APPLY_TO_PPRC_TAKEOVER RESPONSE_NEEDED DEFAULT_HMC_TIMEOUT DEFAULT_HMC_RETRY_COUNT DEFAULT_HMC_RETRY_DELAY DEFAULT_HMCS_LIST ALWAYS_START_RG ADJUST_SPP_SIZE FORCE_SYNC_RELEASE AGREE_TO_COD_COSTS ONOFF_DAYS LPM_POLICY HEARTBEAT_FREQUENCY_DURING_LPM NETWORK_FAILURE_DETECTION_TIME AUTOMATIC_REPOSITORY_REPLACEMENT RESOURCE_ALLOCATION_ORDER HMC_CONNECTION_TYPE CAA_AUTO_START_DR DEFAULT_NOVA_TIMEOUT DEFAULT_NOVA_RETRY_COUNT DEFAULT_NOVA_RETRY_DELAY NOVA_CONNECTION_TYPE CAA_REPOS_MODE CAA_CONFIG_TIMEOUT LVM_PREFERRED_READ CRIT_DAEMON_RESTART_GRACE_PERIOD SKIP_EVENT_PROCESSING_MANAGE_MODE BUCKET_NAME CLOUD_SERVICE USE_EXISTING_BUCKET ONPREM_HYBRID_CLOUD"
    [site]="NAME GID STATE NODES SITE_IP REPOSITORIES RECOVERY_PRIORITY DOMINANT HMCS"
    [node]="NAME HOSTNAME GID STATE RAW_STATE CAA_STATE COMMUNICATION COMMPATH LOCALHOST PERSISTENT_IPS VERSION VERSION_NUMBER EDITION AIX_LEVEL UNSYNCED_CHANGES IPLABEL IPADDRESS TYPE NETWORK NETTYPE NETMASK START_ON_BOOT BROADCAST_ON_START CLINFO_ON_START ENABLE_LIVE_UPDATE HMCS ENABLE_CAA_AFTER_MERGE NOVAS HOST_LABEL CRIT_DAEMON_RESTART_GRACE_PERIOD CLOUD_BASED"
    [host]="HOSTNAME IPADDRESS LOCALHOST HAVERSION VERSION_NUMBER HAEDITION AIX_LEVEL"
    [interface]="NAME INTERFACE IPADDR STATE NETMASK PREFIX NETWORK NODE NETTYPE TYPE ATTR HWADDR"
    [resource_group]="NAME CURRENT_NODE NODES STATE TYPE APPLICATIONS STARTUP FALLOVER FALLBACK NODE_PRIORITY_POLICY NODE_PRIORITY_POLICY_SCRIPT NODE_PRIORITY_POLICY_TIMEOUT DISK VOLUME_GROUP FORCED_VARYON FILESYSTEM FSCHECK_TOOL RECOVERY_METHOD EXPORT_FILESYSTEM SHARED_TAPE_RESOURCES AIX_CONNECTIONS_SERVICES AIX_FAST_CONNECT_SERVICES COMMUNICATION_LINKS MOUNT_FILESYSTEM SERVICE_LABEL MISC_DATA SSA_DISK_FENCING VG_AUTO_IMPORT INACTIVE_TAKEOVER CASCADE_WO_FALLBACK FS_BEFORE_IPADDR NFS_NETWORK MOUNT_ALL_FS WLM_PRIMARY WLM_SECONDARY FALLBACK_AT EXPORT_FILESYSTEM_V4 STABLE_STORAGE_PATH WPAR_NAME VARYON_WITH_MISSING_UPDATES DATA_DIVERGENCE_RECOVERY CURRENT_SECONDARY_NODE SECONDARYNODES SECONDARY_STATE RELATIONSHIP SITE_POLICY MIRROR_GROUP"
    [logical_volume]="NAME TYPE NODES VOLUME_GROUP PHYSICAL_VOLUMES RESOURCE_GROUP MOUNT_POINT LABEL PERMISSION VG_STATE LV_STATE MAX_LPS COPIES LPS PPS PP_SIZE STALE_PPS WRITE_VERIFY MIRROR_WRITE_CONSISTENCY INTER-POLICY INTRA-POLICY RELOCATABLE UPPER_BOUND SCHED_POLICY BB_POLICY EACH_LP_COPY_ON_A_SEPARATE_PV SERIALIZE_IO IDENTIFIER FIRST_COPY_MIRROR_POOL SECOND_COPY_MIRROR_POOL THIRD_COPY_MIRROR_POOL SIZE ENCRYPTION"
    [file_system]="NAME TYPE VFS NODES VOLUME_GROUP RESOURCE_GROUP LOGICAL_VOLUME DEVICE SIZE PERMISSIONS OPTIONS AUTOMOUNT DISK_ACCOUNTING BLOCK_SIZE LV_FOR_LOG INLINE_LOG_SIZE EXT_ATTR_FORMAT ENABLE_QUOTA_MGMT EFS MOUNTGUARD FRAGMENT_SIZE BYTES_PER_INODE ALLOC_GROUP_SIZE"
    [mirror_group]="NAME TYPE MG_TYPE STATE RESOURCE_GROUP MODE RECOVERY RESYNC STORAGE_SYSTEMS CONSISTENT VENDOR_ID HYPERSWAP_ENABLED HYPERSWAP_PRIORITY UNPLANNED_HS_TIMEOUT VOLUME_GROUPS RAW_DISKS SYS_MG_NODE REPOSITORY_MG_SITES REPOSITORY_MG_HS_DISK REPOSITORY_MG_NONHS_DISK MIRROR_PAIRS HORCM_INSTANCE HORCM_TIMEOUT PAIR_EVENT_TIMEOUT ATTRIBUTES"
    [network]="NAME GID NETMASK NET_FAMILY NETWORK_ID BOOT_ADAPTERS SERVICE_ADAPTERS PERSISTENT_ADAPTERS SINGLE_ADAPTER_NETWORK ATTR ALIAS RESOURCE_DIST_PREF SOURCE_IP POLLINTERVAL TYPE GLOBALNAME ALIAS_HB_NETMASK ALIAS_HB_ADDR MONITOR_METHOD PUBLIC INTERFACE INTERFACENAME IPADDR NODE INTERFACETYPE GLOBALNAME HADDR UNSTABLE_THRESHOLD UNSTABLE_PERIOD"
    [physical_volume]="NAME PVID UUID VOLUME_GROUP TYPE DESCRIPTION SIZE AVAILABLE CONCURRENT ENHANCED_CONCURRENT_MODE STATUS SCSIPR_CAPABLE"
    [service_ip]="NAME IPADDR SITE NODE NETMASK PREFIX NETWORK INTERFACE NETTYPE TYPE ATTR GLOBALNAME HWADDR"
    [snapshot]="NAME DESCRIPTION METHODS CAPTURE_DATE CAPTURE_NODE CLUSTER_NAME CLUSTER_TYPE NODES SITES HOSTS SNAPSHOTPATH PAGER SDIFF_OUTPUT_WIDTH SDIFF_FLAGS AVAILABLE_SECTIONS"
    [hmc]="NAME TIMEOUT RETRY_COUNT RETRY_DELAY NODES SITES STATUS VERSION USER_NAME PASSWORD"
    [cod]="NAME USE_DESIRED OPTIMAL_MEM OPTIMAL_CPU OPTIMAL_PU OPTIMAL_VP"
    [nova]="NAME TIMEOUT RETRY_COUNT RETRY_DELAY NODES STATUS VERSION USER_NAME PASSWORD MANAGED_SYSTEM"
)

CL=$LINENO isEnterprise
if (( $? == 1 )); then
    _ATTR_ORDER[resource_group]="NAME CURRENT_NODE NODES STATE CURRENT_SECONDARY_NODE SECONDARYNODES SECONDARY_STATE TYPE APPLICATIONS STARTUP FALLOVER FALLBACK NODE_PRIORITY_POLICY NODE_PRIORITY_POLICY_SCRIPT NODE_PRIORITY_POLICY_TIMEOUT DISK VOLUME_GROUP FORCED_VARYON FILESYSTEM FSCHECK_TOOL RECOVERY_METHOD EXPORT_FILESYSTEM SHARED_TAPE_RESOURCES AIX_CONNECTIONS_SERVICES AIX_FAST_CONNECT_SERVICES COMMUNICATION_LINKS MOUNT_FILESYSTEM SERVICE_LABEL MISC_DATA SSA_DISK_FENCING VG_AUTO_IMPORT INACTIVE_TAKEOVER CASCADE_WO_FALLBACK FS_BEFORE_IPADDR NFS_NETWORK MOUNT_ALL_FS WLM_PRIMARY WLM_SECONDARY FALLBACK_AT RELATIONSHIP SITE_POLICY MIRROR_GROUP EXPORT_FILESYSTEM_V4 STABLE_STORAGE_PATH WPAR_NAME VARYON_WITH_MISSING_UPDATES DATA_DIVERGENCE_RECOVERY"
    _COLON_ATTR_ORDER[resource_group]="NAME CURRENT_NODE NODES STATE SECONDARYNODES SECONDARY_STATE TYPE APPLICATIONS STARTUP FALLOVER FALLBACK NODE_PRIORITY_POLICY DISK VOLUME_GROUP FORCED_VARYON FILESYSTEM FSCHECK_TOOL RECOVERY_METHOD EXPORT_FILESYSTEM SHARED_TAPE_RESOURCES AIX_CONNECTIONS_SERVICES AIX_FAST_CONNECT_SERVICES COMMUNICATION_LINKS MOUNT_FILESYSTEM SERVICE_LABEL MISC_DATA SSA_DISK_FENCING VG_AUTO_IMPORT INACTIVE_TAKEOVER CASCADE_WO_FALLBACK FS_BEFORE_IPADDR NFS_NETWORK MOUNT_ALL_FS WLM_PRIMARY WLM_SECONDARY FALLBACK_AT RELATIONSHIP SITE_POLICY MIRROR_GROUP EXPORT_FILESYSTEM_V4 STABLE_STORAGE_PATH WPAR_NAME VARYON_WITH_MISSING_UPDATES DATA_DIVERGENCE_RECOVERY NODE_PRIORITY_POLICY_SCRIPT NODE_PRIORITY_POLICY_TIMEOUT CURRENT_SECONDARY_NODE"
fi

#=====================================================================
# To preserve backwards compatibility with previous versions of clvt
# establish a keyword compatibility map. Each key is the old way of
# specifying an attribute, and each value is the new attribute name.
# All this is necessary to avoid breaking migration (what happens if
# the customer migrations the base product and clvt, but *not* their
# existing Smart Assists?). Of course, perhaps even more important,
# and certainly more likely to occur, is avoiding breaking any pre-
# existing customer scripts! An angry customer is a scary customer!!
#=====================================================================
typeset -A COMPAT
COMPAT=(
    [PRIMARYNODES]="NODES"
    [NEWNAME]="NAME"
    [RG]="RESOURCE_GROUP"
    [VG]="VOLUME_GROUP"
    [CONCURRENT_VOLUME_GROUP]="VOLUME_GROUP"
    [LV]="LOGICAL_VOLUME"
    [PV]="PHYSICAL_VOLUME"
    [NW]="NETWORK"
    [IF]="INTERFACE"
    [DISK]="PHYSICAL_VOLUME"
    [DISKS]="PHYSICAL_VOLUMES"
    [FS]="FILE_SYSTEM"
    [PREFIXLENGTH]="PREFIX"
    [RESOURCE_MODE]="MODE"
    [APPLICATIONMONITORTYPE]="TYPE"
    [NETWORKTYPE]="TYPE"
    [ASSOCIATED_MONITORS]="MONITORS"
    [ASSOCIATEDMONITORS]="MONITORS"
    [NODENAME]="NODE"
    [ISPROPOGATEFILEDURINGSYNC]="SYNC_WITH_CLUSTER"
    [ISPROPOGATEDFILEDURINGSYNC]="SYNC_WITH_CLUSTER"
    [ISPROPOGATEAUTODETECTED]="SYNC_WHEN_CHANGED"
    [ISPROPOGATEDAUTODETECTED]="SYNC_WHEN_CHANGED"
    [ISPROPOGATEAUTOWHENDETECTED]="SYNC_WHEN_CHANGED"
    [PRIMARY_NODES]="PRIMARYNODES"
    [SECONDARY_NODES]="SECONDARYNODES"
    [TIMER]="FALLBACK_AT"

    [UPPER_BOUND]="MAX_PVS_FOR_NEW_ALLOC"
    [INTER_POLICY]="PV_RANGE"
    [INTRA_POLICY]="POSITION"
    [LPS]="LOGICAL_PARTITIONS"
    [PPS]="PHYSICAL_PARTITIONS"
    [COPIES]="LPART_COPIES"
    [MAX_LPS]="MAX_LPARTS"
    [SCHED_POLICY]="SCHEDULING_POLICY"
    [BB_POLICY]="BAD_BLOCK_RELOCATION"
    [WRITE_VERIFY]="VERIFY_WRITES"
    [EACH_LP_COPY_ON_A_SEPARATE_PV]="LPARTS_ON_SEPARATE_PVS"
    [RELOCATABLE]="RELOCATE"
    [MIRROR_WRITE_CONSISTENCY]="WRITE_CONSISTENCY"

    [SYS_MG_NODE]="NODE"
    [RAW_DISKS]="DISKS"
    [REPOSITORY_MG_SITES]="SITE"
    [REPOSITORY_HS_DISK]="HS_DISK"
    [REPOSITORY_NONHS_DISK]="NON_HS_DISK"

    [REPOSITORY]="REPOSITORIES"
    [RESTART_COUNT]="RESTARTCOUNT"
    [FAILURE_ACTION]="FAILUREACTION"
    [MONITOR_INTERVAL]="MONITORINTERVAL"
    [HUNG_SIGNAL]="HUNGSIGNAL"
    [HUNG_MONITOR_SIGNAL]="HUNGSIGNAL"
    [RESTART_INTERVAL]="RESTARTINTERVAL"
    [NOTIFY_METHOD]="NOTIFYMETHOD"
    [CLEANUP_METHOD]="CLEANUPMETHOD"
    [RESTART_METHOD]="RESTARTMETHOD"
    [MONITOR_RETRY_COUNT]="MONITORRETRYCOUNT"
    [AM_LOGGING]="AMLOGGING"
    [ENABLE_EFS]="EFS"
);

#
# The API will supply default values to be used when an imprecise definition
# is given
#
typeset PROGNAME=${0##*/}
if [[ -f $CLMGR_TMPLOG ]]
then
    # Let's log the warnings if we can
    export PATH="$(/usr/es/sbin/cluster/utilities/cl_get_path all 2>>$CLMGR_TMPLOG)"
else
    export PATH="$(/usr/es/sbin/cluster/utilities/cl_get_path all 2>/dev/null)"
fi
export CLMGR_DEFAULT_CLUSTER_NAME="$(uname -n)_cluster"
typeset STATUS=0
typeset -l RESOURCE_CLASS
if [[ -n $1 && $1 != *=* ]]; then
    RESOURCE_CLASS=$1
    shift
    if [[ -n $1 && $1 != *=* ]]; then
        # Resource name is optional for operations such as "add cluster"
        RESOURCE_NAME=$1
        shift
    fi
fi

CL=$LINENO . $HAEVENTS/class_processors "$_ACTION"

#=======================================================================
# This is the global set of *all* accepted attributes. If an attribute
# is *not* in this list, then clvt will *not* recognize it.
#=======================================================================
typeset _VAR_SUBSTITUTIONS=
if [[ $_ACTION == "manage" ]]; then
    _VAR_SUBSTITUTIONS=${_CLASS_PROCESSORS[${_SUB_ACTION}_${RESOURCE_CLASS}]}
elif [[ $_ACTION == @(query|list) ]]; then
    _VAR_SUBSTITUTIONS=${_CLASS_PROCESSORS[$RESOURCE_CLASS]}
    _VAR_SUBSTITUTIONS="$_VAR_SUBSTITUTIONS ${_COLON_ATTR_ORDER[${RESOURCE_CLASS#@(query|list)_}]}"
else
    _VAR_SUBSTITUTIONS=${_CLASS_PROCESSORS[$RESOURCE_CLASS]}
    _VAR_SUBSTITUTIONS=${_VAR_SUBSTITUTIONS#*[[:space:]]} # Remove script/func name
fi
_VAR_SUBSTITUTIONS=${_VAR_SUBSTITUTIONS#+([[:space:]])/ }

#==================================================================
# Determine if this processor requires a specified resource label
#==================================================================
resourceIsRequired=0
if [[ ${_CLASS_PROCESSORS[$RESOURCE_CLASS]} == *[[:space:]]RESOURCE_NAME* ]]
then
    resourceIsRequired=0
fi
export resourceIsRequired

#==========================================================================
# For the "query" action, in order to support filtering, it is necessary
# to know what attributes are available to filter/search on. Rather than
# force each attribute to be added to the class processor for the class'
# query action (defined in "events/class_processors"), which is a bit
# redundant and cumbersome , the available attributes are retrieved here,
# then automatically appended to the class processor for the query.
#==========================================================================
if [[ $_ACTION == "query" && \
      $RESOURCE_CLASS == query_* && \
      -n ${_CLASS_PROCESSORS[$RESOURCE_CLASS]} ]]
then
    typeset classname=${RESOURCE_CLASS#${_ACTION}_}
    for attr in ${_ATTR_ORDER[$classname]}; do
        _VAR_SUBSTITUTIONS="$_VAR_SUBSTITUTIONS $attr"
    done
fi

#====================================
# Make sure there are no duplicates
#====================================
typeset _VAR= _TEMP=
for _VAR in $_VAR_SUBSTITUTIONS; do
    [[ " $_TEMP " == *\ $_VAR\ * ]] && continue
    [[ -n $_TEMP ]] && _TEMP="$_TEMP "
    _TEMP="$_TEMP$_VAR"
done
_VAR_SUBSTITUTIONS=$_TEMP
unset _VAR _TEMP

FPATH=/usr/es/lib/ksh93/hacmp
FPATH=$FPATH:/usr/es/lib/ksh93/aix
FPATH=$FPATH:/usr/es/lib/ksh93/util

#================================================================
# Check for a cry for help from the customer! We accept h,H,*,?
# to ask for help. We also accept an optional "v", but that is
# intended for internal use only (because the text it displays
# is *not* translated), and is not documented.
#================================================================
typeset -l lcarg= lcargs=
for lcarg in $_EVENT_SCRIPT_ARGS; do
    [[ $lcarg == *=* ]] && continue
    [[ $lcarg != -* && $lcarg != @(\*|\?) ]] && continue
    lcarg=${lcarg##*-}  # Strip off any leading dashes
    lcargs="$lcargs $lcarg"

    # Slow down the loop a fraction, to avoid spiking the CPU
    LANG=C sleep .001
done
for lcarg in $lcargs; do
    [[ $lcarg == *@(h|\*|\?)* && $_ACTION != "runcmd" ]] && CLMGR_HELP=1
    [[ $lcarg == *v*                                  ]] && CLMGR_VERBOSE=1

    # Slow down the loop a fraction, to avoid spiking the CPU
    LANG=C sleep .001
done

# Construct the inline replacement hash array for the optional arguments
typeset -i BAD_OPTS=0
if (( ! CLMGR_HELP )); then
    CL=$LINENO KLIB_UTIL_parse_arguments _ENV_ARGS "$@"
    if (( $? != RC_SUCCESS )); then
        cl_dspmsg -s $CLMGR_SET $CLMGR_MSGS 61 "\nERROR: an error occurred whiile parsing the inputs for the operation.\n\n" 1>&2
        [[ -n $CLMGR_COMMAND ]] && print -u2 "       $CLMGR_COMMAND\n"
        log_return_msg "$RC_ERROR" resource_common "$LINENO"
        exit $?
    fi

    if [[ $_ACTION != "runcmd" ]] && \
       [[ $_ACTION != "query" && $RESOURCE_CLASS != "class" ]]
    then
        for key in ${!_ENV_ARGS[*]}; do
            [[ $key == @(RESOURCE_NAME|RESOURCE_CLASS|properties) ]] && continue
            CL=$LINENO expand_keyword "$key" _VAR_SUBSTITUTIONS
            (( $? != 0 )) && (( BAD_OPTS++ ))

            # Slow down the loop a fraction, to avoid spiking the CPU
            LANG=C sleep .001
        done
    fi
fi

#=========================================================
: If bad options were detected, or help was requested by
: the customer display the available options, then exit.
: Emphasis on the latter... if these conditions are met,
: the code wil *always* exit here.
#=========================================================
if (( BAD_OPTS || CLMGR_HELP )); then
    typeset class=$RESOURCE_CLASS
    typeset SRC_FILE=""

    print "$SENTINEL COMMAND  ($CLMGR_TRANSACTION_ID):  ${CLMGR_COMMAND/ -T $CLMGR_TRANSACTION_ID}" >>$CLMGR_TMPLOG

    if (( ! CLMGR_VERBOSE || BAD_OPTS )); then
        print "$SENTINEL INTERNAL ($CLMGR_TRANSACTION_ID):  [display_operation_syntax || display_simple_help]" >>$CLMGR_TMPLOG
        display_operation_syntax $BAD_OPTS
        (( $? != RC_SUCCESS )) && display_simple_help
    else
        print "$SENTINEL INTERNAL ($CLMGR_TRANSACTION_ID):  [display_operation_man_page || display_operation_syntax || display_simple_help]" >>$CLMGR_TMPLOG
        display_operation_man_page
        if (( $? != RC_SUCCESS )); then
            display_operation_syntax $BAD_OPTS
            (( $? != RC_SUCCESS )) && display_simple_help
        fi
    fi
    CLMGR_STACK=""  # Avoid writing the stack to the log (implies an error)
    (( CLMGR_HELP )) && exit $RC_SUCCESS || exit $RC_INCORRECT_INPUT
fi

_ENV_ARGS[RESOURCE_NAME]=$RESOURCE_NAME
_ENV_ARGS[RESOURCE_CLASS]=$RESOURCE_CLASS

# If the user doesn't specify a cluster name, use the default name
if [[ "$RESOURCE_CLASS" == "cluster" ]]; then
    [[ -z "$RESOURCE_NAME" ]] && _ENV_ARGS[RESOURCE_NAME]=$CLMGR_DEFAULT_CLUSTER_NAME
fi

log_return_msg "$rc" resource_common "$LINENO"
return $?


#==============================================================================
# The following, comment block attempts to enforce coding standards when this
# file is edited via emacs or vim. This block _must_ appear at the very end
# of the file, or the editor will not find it, and it will be ignored.
#==============================================================================
# Local Variables:
# indent-tabs-mode: nil
# tab-width: 4
# End:
#==============================================================================
# vim: tabstop=4 shiftwidth=4 expandtab
#==============================================================================
