#!/bin/bash
# init fragment for O2CB.
#
# chkconfig: 2345 24 19
# description: Load O2CB cluster services at system boot.
#
### BEGIN INIT INFO
# Provides: o2cb
# Required-Start: $network
# Should-Start:
# Required-Stop:
# Default-Start: 2 3 5
# Default-Stop:
# Short-Description: Load O2CB cluster services at system boot.
# Description: Load O2CB cluster services at system boot.
### END INIT INFO

# Force LC_ALL=C
export LC_ALL=C

# Let's try to use the LSB functions
. /lib/lsb/init-functions
if [ $? != 0 ]
then
    echo "Unable to load LSB init functions" >&2
    exit 1
fi

CLUSTERCONF=/etc/ocfs2/cluster.conf

OCFS2_SYS_DIR="/sys/fs/ocfs2"
LOADED_PLUGINS_FILE="${OCFS2_SYS_DIR}/loaded_cluster_plugins"
CLUSTER_STACK_FILE="${OCFS2_SYS_DIR}/cluster_stack"
DLMFS_CAPABILITIES_FILE='/sys/module/ocfs2_dlmfs/parameters/capabilities'
DEBUGFS_DIR="/sys/kernel/debug"

if [ -f /etc/sysconfig/o2cb ]
then
    # Red Hat and Novell
    CONFIGURATION=/etc/sysconfig/o2cb
elif [ -f /etc/default/o2cb ]
then
    # Debian
    CONFIGURATION=/etc/default/o2cb
    DPKGRECONF=yes
elif [ -d /etc/sysconfig ]
then
    CONFIGURATION=/etc/sysconfig/o2cb
else
    CONFIGURATION=/etc/default/o2cb
fi

# How long to wait for things to start
BRINGUP_TIMEOUT=5

# The default values should always be in sync with the kernel
DEF_HEARTBEAT_THRESHOLD=31
DEF_IDLE_TIMEOUT_MS=30000
DEF_KEEPALIVE_DELAY_MS=2000
DEF_RECONNECT_DELAY_MS=2000

# Minimum timeout values
MIN_HEARTBEAT_THRESHOLD=7
MIN_IDLE_TIMEOUT_MS=5000
MIN_KEEPALIVE_DELAY_MS=1000
MIN_RECONNECT_DELAY_MS=2000

VERSION=@@VERSION@@

# Source configuration
[ -f "${CONFIGURATION}" ] && . "${CONFIGURATION}"
# Need this default
[ -z "$O2CB_STACK" ] && O2CB_STACK=o2cb

configfs_path()
{
    # Note that this is only valid *after* configfs is loaded
    if [ -d /sys/kernel/config ]
    then
        echo /sys/kernel/config
    else
        echo /config
    fi
}


#
# if_fail()
#
# Evaluates return codes.  If 0, prints "OK", if 1, prints "Failed"
# and exits.  If 2, status is "already done" and nothing is printed.
# The rest of the functions in here all honor this convention.
#
if_fail()
{
    RC="$1"
    REASON="$2"
    if [ "$RC" = "0" ]
    then
        echo "OK"
        return
    elif [ "$RC" = "2" ]
    then
        return
    fi
    echo "Failed"
    if [ -n "${REASON}" ]
    then
        echo "${REASON}" >&2
    fi
    exit 1
}


#
# write_sysconfig()
#
# Writes the system configuration out
#
write_sysconfig()
{
    echo -n "Writing O2CB configuration: "
    cat >"${CONFIGURATION}" <<EOF
#
# This is a configuration file for automatic startup of the O2CB
# driver.  It is generated by running /etc/init.d/o2cb configure.
# On Debian based systems the preferred method is running
# 'dpkg-reconfigure ocfs2-tools'.
#

# O2CB_ENABLED: 'true' means to load the driver on boot.
O2CB_ENABLED=${O2CB_ENABLED:-false}

# O2CB_STACK: The name of the cluster stack backing O2CB.
O2CB_STACK=${O2CB_STACK}

# O2CB_BOOTCLUSTER: If not empty, the name of a cluster to start.
O2CB_BOOTCLUSTER=${O2CB_BOOTCLUSTER}

# O2CB_HEARTBEAT_THRESHOLD: Iterations before a node is considered dead.
O2CB_HEARTBEAT_THRESHOLD=${O2CB_HEARTBEAT_THRESHOLD}

# O2CB_IDLE_TIMEOUT_MS: Time in ms before a network connection is considered dead.
O2CB_IDLE_TIMEOUT_MS=${O2CB_IDLE_TIMEOUT_MS}

# O2CB_KEEPALIVE_DELAY_MS: Max time in ms before a keepalive packet is sent
O2CB_KEEPALIVE_DELAY_MS=${O2CB_KEEPALIVE_DELAY_MS}

# O2CB_RECONNECT_DELAY_MS: Min time in ms between connection attempts
O2CB_RECONNECT_DELAY_MS=${O2CB_RECONNECT_DELAY_MS}

EOF

    if [ $? != 0 ]
    then
        return 1
    fi
    return 0
}

#
# read_timeout()
#   Returns timeout value provided by user to caller in RET_VAL
#
read_timeout()
{
    if [ "$#" -lt "4" ]; then
        echo "read_timeout(): Requires more arguments" >&2
        exit 1
    fi

    ATTRIB_NAME=$1
    ATTRIB_VAL=$2
    MIN_ATTRIB_VAL=$3
    DEF_ATTRIB_VAL=$4
    RET_VAL=0

    while :
    do
        if [ -z $ATTRIB_VAL ]; then
            CUR=$DEF_ATTRIB_VAL
        else
            CUR=$ATTRIB_VAL
        fi
        echo -n "Specify ${ATTRIB_NAME} (>=$MIN_ATTRIB_VAL) [$CUR]: "
        read LINE
        case "$LINE" in
        "")
            RET_VAL="$ATTRIB_VAL"
            break
            ;;

        *[^0-9]*)
            echo "Invalid ${ATTRIB_NAME} value: $LINE" >&2
            ;;
        *)
            if [ $LINE -lt $MIN_ATTRIB_VAL ]; then
                echo "${ATTRIB_NAME} cannot be less than $MIN_ATTRIB_VAL" >&2
            else
                RET_VAL="$LINE"
                break
            fi
            ;;
        esac
    done
}

set_timeouts_o2cb()
{
    O2CB_HEARTBEAT_THRESHOLD_FILE_OLD=/proc/fs/ocfs2_nodemanager/hb_dead_threshold
    O2CB_HEARTBEAT_THRESHOLD_FILE=$(configfs_path)/cluster/${CLUSTER}/heartbeat/dead_threshold
    if [ -n "$O2CB_HEARTBEAT_THRESHOLD" ]; then
        if [ -f "$O2CB_HEARTBEAT_THRESHOLD_FILE" ]; then
            echo "$O2CB_HEARTBEAT_THRESHOLD" > "$O2CB_HEARTBEAT_THRESHOLD_FILE"
        elif [ -f "$O2CB_HEARTBEAT_THRESHOLD_FILE_OLD" ]; then
            echo "$O2CB_HEARTBEAT_THRESHOLD" > "$O2CB_HEARTBEAT_THRESHOLD_FILE_OLD"
        fi
    fi

    O2CB_IDLE_TIMEOUT_MS_FILE=$(configfs_path)/cluster/${CLUSTER}/idle_timeout_ms
    if [ -n "$O2CB_IDLE_TIMEOUT_MS" ]; then
        if [ -f "$O2CB_IDLE_TIMEOUT_MS_FILE" ]; then
            echo "$O2CB_IDLE_TIMEOUT_MS" > "$O2CB_IDLE_TIMEOUT_MS_FILE"
        fi
    fi

    O2CB_KEEPALIVE_DELAY_MS_FILE=$(configfs_path)/cluster/${CLUSTER}/keepalive_delay_ms
    if [ -n "$O2CB_KEEPALIVE_DELAY_MS" ]; then
        if [ -f "$O2CB_KEEPALIVE_DELAY_MS_FILE" ]; then
            echo "$O2CB_KEEPALIVE_DELAY_MS" > "$O2CB_KEEPALIVE_DELAY_MS_FILE"
        fi
    fi

    O2CB_RECONNECT_DELAY_MS_FILE=$(configfs_path)/cluster/${CLUSTER}/reconnect_delay_ms
    if [ -n "$O2CB_RECONNECT_DELAY_MS" ]; then
        if [ -f "$O2CB_RECONNECT_DELAY_MS_FILE" ]; then
            echo "$O2CB_RECONNECT_DELAY_MS" > "$O2CB_RECONNECT_DELAY_MS_FILE"
        fi
    fi
}

show_timeouts_o2cb()
{

    O2CB_HEARTBEAT_THRESHOLD_FILE_OLD=/proc/fs/ocfs2_nodemanager/hb_dead_threshold
    O2CB_HEARTBEAT_THRESHOLD_FILE=$(configfs_path)/cluster/${CLUSTER}/heartbeat/dead_threshold
    if [ -f "$O2CB_HEARTBEAT_THRESHOLD_FILE" ]; then
        VAL=`cat "$O2CB_HEARTBEAT_THRESHOLD_FILE"`
        echo "  Heartbeat dead threshold: ${VAL}"
    elif [ -f "$O2CB_HEARTBEAT_THRESHOLD_FILE_OLD" ]; then
        VAL=`cat "$O2CB_HEARTBEAT_THRESHOLD_FILE_OLD"`
        echo "  Heartbeat dead threshold: ${VAL}"
    fi

    O2CB_IDLE_TIMEOUT_MS_FILE=$(configfs_path)/cluster/${CLUSTER}/idle_timeout_ms
    if [ -f "$O2CB_IDLE_TIMEOUT_MS_FILE" ]; then
        VAL=`cat "$O2CB_IDLE_TIMEOUT_MS_FILE"`
        echo "  Network idle timeout: ${VAL}"
    fi

    O2CB_KEEPALIVE_DELAY_MS_FILE=$(configfs_path)/cluster/${CLUSTER}/keepalive_delay_ms
    if [ -f "$O2CB_KEEPALIVE_DELAY_MS_FILE" ]; then
        VAL=`cat "$O2CB_KEEPALIVE_DELAY_MS_FILE"`
        echo "  Network keepalive delay: ${VAL}"
    fi

    O2CB_RECONNECT_DELAY_MS_FILE=$(configfs_path)/cluster/${CLUSTER}/reconnect_delay_ms
    if [ -f "$O2CB_RECONNECT_DELAY_MS_FILE" ]; then
        VAL=`cat "$O2CB_RECONNECT_DELAY_MS_FILE"`
        echo "  Network reconnect delay: ${VAL}"
    fi
}

#
# return 1 if global heartbeat enabled
#
global_heartbeat_enabled_o2cb()
{
    O2CB_HEARTBEAT_MODE_FILE=$(configfs_path)/cluster/${CLUSTER}/heartbeat/mode
    VAL="local"
    if [ -f "$O2CB_HEARTBEAT_MODE_FILE" ]; then
        VAL=$(cat "$O2CB_HEARTBEAT_MODE_FILE")
    fi
    if [ "${VAL}" = "global" ]; then
        return 1;
    fi
    return 0;
}

#
# configure_ask()
#
# Ask configuration questions, setting the shell vars.
#
configure_ask()
{
    if [ -n "$DPKGRECONF" ]; then
	cat <<EOF
Configuring the O2CB driver.

Please configure this service in the Debian way:

 dpkg-reconfigure ocfs2-tools
EOF

	exit 0
    fi

    cat <<EOF
Configuring the O2CB driver.

This will configure the on-boot properties of the O2CB driver.
The following questions will determine whether the driver is loaded on
boot.  The current values will be shown in brackets ('[]').  Hitting
<ENTER> without typing an answer will keep that current value.  Ctrl-C
will abort.

EOF

    while :
    do
        if [ "$O2CB_ENABLED" = "true" ]
        then
            CUR=y
        else
            CUR=n
        fi
        echo -n "Load O2CB driver on boot (y/n) [$CUR]: "
        read LINE
        case "$LINE" in
        "")
            break
            ;;
        y|Y)
            O2CB_ENABLED=true
            break
            ;;
        n|N)
            O2CB_ENABLED=false
            break
            ;;
        *)
            echo "Invalid answer: $LINE" >&2
            ;;
        esac
    done

    while :
    do
        echo -n "Cluster stack backing O2CB [$O2CB_STACK]: "
        read LINE
        case "$LINE" in
        "")
            break
            ;;
        ????)
            O2CB_STACK="$LINE"
            break
            ;;
        *)
            echo "Invalid answer: $LINE" >&2
            ;;
        esac
    done

    while :
    do
        echo -n "Cluster to start on boot (Enter \"none\" to clear) [$O2CB_BOOTCLUSTER]: "
        read LINE
        case "$LINE" in
        "")
            break
            ;;
        none)
            O2CB_BOOTCLUSTER=
            break
            ;;

        *[^a-zA-Z0-9]*)
            echo "Invalid cluster name: $LINE" >&2
            ;;
        *)
            O2CB_BOOTCLUSTER="$LINE"
            break
            ;;
        esac
    done

    read_timeout "heartbeat dead threshold" "$O2CB_HEARTBEAT_THRESHOLD" "$MIN_HEARTBEAT_THRESHOLD" "$DEF_HEARTBEAT_THRESHOLD"
    O2CB_HEARTBEAT_THRESHOLD="$RET_VAL"

    read_timeout "network idle timeout in ms" "$O2CB_IDLE_TIMEOUT_MS" "$MIN_IDLE_TIMEOUT_MS" "$DEF_IDLE_TIMEOUT_MS"
    O2CB_IDLE_TIMEOUT_MS="$RET_VAL"

    read_timeout "network keepalive delay in ms" "$O2CB_KEEPALIVE_DELAY_MS" "$MIN_KEEPALIVE_DELAY_MS" "$DEF_KEEPALIVE_DELAY_MS"
    O2CB_KEEPALIVE_DELAY_MS="$RET_VAL"

    read_timeout "network reconnect delay in ms" "$O2CB_RECONNECT_DELAY_MS" "$MIN_RECONNECT_DELAY_MS" "$DEF_RECONNECT_DELAY_MS"
    O2CB_RECONNECT_DELAY_MS="$RET_VAL"

    # XXX ask about mount point base
}


#
# make_dir()
#
# Create $1
# Returns 0 on success, 1 on error, 2 if it already exists.
#
make_dir()
{
    if [ "$#" -lt "1" -o -z "$1" ]
    then
        echo "make_dir(): Requires an argument" >&2
        return 1
    fi
    DIR="$1"
    if [ -e "$DIR" ]
    then
        if [ -d "$DIR" ]
        then
            return 2
        fi
        echo "make_dir(): File $DIR is not a directory" >&2
        return 1
    fi

    echo -n "Creating directory '$DIR': "
    mkdir -p "$DIR" 2>/dev/null
    if [ $? != 0 ]
    then
        echo "Unable to create directory '$DIR'" >&2
        return 1
    fi
    return 0
}


#
# driver_filesystem()
# Check to see if a filesystem driver is loaded.
#
# 0 is loaded, 1 is not.
#
driver_filesystem()
{
    if [ -z "$1" ]
    then
        echo "driver_filesystem(): Missing an argument" >&2
        exit 1
    fi
    FSNAME="$1"
    PSEUDO="$2"

    FSOUT="$(awk '(NF == 1 && $1 ~ /^'$FSNAME'$/) || $2 ~ /^'$FSNAME'$/{
                      print $1;exit
                  }' /proc/filesystems 2>/dev/null)"

    test -n "$FSOUT"
    return $?
}


#
# load_filesystem()
# Load a filesystem driver.
#
# 0 is success, 1 is error, 2 is already loaded.
#
load_filesystem()
{
    if [ "$#" != "1" -o -z "$1" ]
    then
        echo "load_filesystem(): Missing an argument" >&2
        return 1
    fi
    FSNAME="$1"

    driver_filesystem "$FSNAME" && return 2

    echo -n "Loading filesystem \"$FSNAME\": "
    modprobe -s "$FSNAME"
    if [ "$?" != 0 ]
    then
        echo "Unable to load filesystem \"$FSNAME\"" >&2
        return 1
    fi

    return 0
}

#
# unload_filesystem()
# Unload a filesystem driver.  Be careful to notice if the driver is
# built-in and do nothing.
#
# 0 is success, 1 is error, 2 is already loaded.
#
unload_filesystem()
{
    if [ "$#" != "1" -o -z "$1" ]
    then
        echo "unload_filesystem(): Missing an argument" >&2
        return 1
    fi
    FSNAME="$1"

    driver_filesystem "$FSNAME" || return 2

    MODOUT="`awk '$1 ~ /^'$FSNAME'$/{print $1,$3;exit}' < /proc/modules 2>/dev/null`"
    if [ -z "$MODOUT" ]
    then
        # The driver is built in, we can't unload it.
        return 2
    fi

    case "$MODOUT" in
    $FSNAME\ 0)
        ;;
    $FSNAME\ *)
        # The driver is busy, leave it alone
        return 2
        ;;
    *)
        echo -n "Invalid module parsing! "
        return 1
        ;;
    esac

    echo -n "Unloading module \"$FSNAME\": "
    modprobe -rs "$FSNAME"
    if [ "$?" != 0 ]
    then
        echo "Unable to unload module \"$FSNAME\"" >&2
        return 1
    fi

    return 0
}

#
# check_filesystem()
# Check to see if a filesystem of type $1 is mounted at $2.
#
# 0 is mounted, 1 is not.
#
check_filesystem()
{
    if [ "$#" != "2" -o -z "$1" -o -z "$2" ]
    then
        echo "check_filesystem(): Missing arguments" >&2
        exit 1
    fi
    FSNAME="$1"
    MOUNTPOINT="$2"

    FULL_MOUNTSEARCH="`echo "$MOUNTPOINT" | sed -e 's/\//\\\\\//g'`"
    MOUNTOUT="`awk '$2 ~ /^'$FULL_MOUNTSEARCH'$/ && $3 ~ /^'$FSNAME'$/{print $2; exit}' < /proc/mounts 2>/dev/null`"
    test -n "$MOUNTOUT"
    return $?
}

#
# mount_filesystem()
# Mounts a pseudo-filesystem of type $1 on mountpoint $2.  It will
# load the drivers for $1 and create $2 if needed.
#
# 0 is success, 1 is error, 2 is already mounted.
#
mount_filesystem()
{
    if [ "$#" != "2" -o -z "$1" -o -z "$2" ]
    then
        echo "mount_filesystem(): Missing arguments" >&2
        return 1
    fi
    FSNAME="$1"
    MOUNTPOINT="$2"

    check_filesystem "$FSNAME" "$MOUNTPOINT" && return 2

    load_filesystem "$FSNAME"
    if_fail $?

    # XXX some policy?
    if [ ! -e "$MOUNTPOINT" ]; then
        make_dir $MOUNTPOINT
        if_fail "$?"
    fi

    echo -n "Mounting ${FSNAME} filesystem at ${MOUNTPOINT}: "
    mount -t ${FSNAME} ${FSNAME} ${MOUNTPOINT}
    if [ $? != 0 ]
    then
        echo "Unable to mount ${FSNAME} filesystem" >&2
        return 1
    fi

    return 0
}

#
# unmount_filesystem()
# Unmount a pseudo-filesystem of type $1 from mountpoint $2.  It will
# remove the driver for $1 if it can.
#
# 0 is success, 1 is error, 2 is not mounted
#
unmount_filesystem()
{
    if [ "$#" != "2" -o -z "$1" -o -z "$2" ]
    then
        echo "unmount_filesystem(): Missing arguments" >&2
        return 1
    fi
    FSNAME="$1"
    MOUNTPOINT="$2"

    if check_filesystem "$FSNAME" "$MOUNTPOINT"
    then
        echo -n "Unmounting ${FSNAME} filesystem: "
        umount $MOUNTPOINT
        RC=$?
        if [ $RC != 0 ]
        then
            echo "Unable to unmount ${FSNAME} filesystem" >&2
            return 1
        fi
        if_fail $RC  # For the success string
    fi

    unload_filesystem "$FSNAME"
    return $?
}

#
# status_filesystem()
# Report the status of a filesystem, whether it is mounted or not
#
# 0 is not mounted, 1 is error, 2 is already mounted
#
status_filesystem()
{
    if [ "$#" != "2" -o -z "$1" -o -z "$2" ]
    then
        echo "status_filesystem(): Missing arguments" >&2
        return 1
    fi
    FSNAME="$1"
    MOUNTPOINT="$2"

    echo -n "Driver for \"$FSNAME\": "
    if driver_filesystem "$FSNAME"
    then
        echo "Loaded"
    else
        echo "Not loaded"
        return 0
    fi

    echo -n "Filesystem \"$FSNAME\": "
    if check_filesystem "$FSNAME" "$MOUNTPOINT"
    then
        echo "Mounted"
        return 2
    fi
    echo "Not mounted"
    return 0
}

status_daemon()
{
    DAEMON="/sbin/ocfs2_controld.${O2CB_STACK}"
    echo -n "Checking for control daemon: "
    if [ -n "$(pidofproc "$DAEMON")" ]
    then
        echo "$DAEMON"
        return 0
    else
        echo "not running"
        return 1
    fi
}

bringup_daemon()
{
    DAEMON="/sbin/ocfs2_controld.${O2CB_STACK}"
    echo -n "Starting $(basename "$DAEMON"): "
    start_daemon "$DAEMON"
    [ $? != 0 ] && return 1

    COUNT=0
    while [ -z "$(pidofproc "$DAEMON")" ]
    do
        COUNT="$(expr "$COUNT" + 1)"
        if [ "$COUNT" -gt "$BRINGUP_TIMEOUT" ]
        then
            return 1
        fi
        sleep 1
    done

    return 0
}

kill_daemon()
{
    SIGNAL="$1"
    DAEMON="/sbin/ocfs2_controld.${O2CB_STACK}"

    status_daemon >/dev/null 2>&1 || return 2

    PID="$(pidofproc "$DAEMON")"

    echo -n "Stopping $(basename "$DAEMON"): "
    killproc "$DAEMON" $SIGNAL

    TRIED=
    while :
    do
        NEWPID="$(pidofproc "$DAEMON")"
        if [ -z "$NEWPID" -o "$NEWPID" != "$PID" ]
        then
            echo "Stopped"
            return
        fi

        [ -n "$TRIED" ] && break
        TRIED=t

        # Give it one chance to exit
        sleep 1
    done

    echo "Failed"
    return 1
}

#
# list_nodes_o2cb()
#
list_nodes_o2cb()
{
    check_filesystem "debugfs" "$DEBUGFS_DIR"
    if [ "$?" -ne "0" ]
    then
        return
    fi

    nodestr=$(cat $DEBUGFS_DIR/o2hb/livenodes)
    echo "Nodes in O2CB cluster: $nodestr"
}

#
# list_heartbeat_o2cb()
#
list_heartbeat_o2cb()
{
    if [ "$#" -lt "1" -o -z "$1" ]
    then
        echo "list_heartbeat_o2cb(): Requires an argument" >&2
        return 1
    fi
    CLUSTER="$1"

    RC=0
    if [ -d "$(configfs_path)/cluster/${CLUSTER}/heartbeat/" ]
    then
        ls -1 "$(configfs_path)/cluster/${CLUSTER}/heartbeat/" | while read HBUUID
        do
            if [ -d "$(configfs_path)/cluster/${CLUSTER}/heartbeat/${HBUUID}" ]
            then
                dev=$(cat "$(configfs_path)/cluster/${CLUSTER}/heartbeat/${HBUUID}/dev")
                echo "  ${regionstr}${HBUUID} /dev/${dev}"
            fi
        done
    fi
}


#
# check_heartbeat_o2cb()
#
# 0 is hb not active, 1 is error, 2 is hb active
#
check_heartbeat_o2cb()
{
    if [ "$#" -lt "1" -o -z "$1" ]
    then
        echo "check_heartbeat_o2cb(): Requires an argument" >&2
        return 1
    fi
    CLUSTER="$1"

    RC=0
    if [ -d "$(configfs_path)/cluster/${CLUSTER}/heartbeat/" ]
    then
        ls -1 "$(configfs_path)/cluster/${CLUSTER}/heartbeat/" | while read HBUUID
        do
            if [ -d "$(configfs_path)/cluster/${CLUSTER}/heartbeat/${HBUUID}" ]
            then
                return 2;
            fi
        done
        if [ $? = 2 ]
        then
            RC=2
        fi
    fi

    return $RC
}

#
# clean_heartbeat_o2cb()
# Removes the inactive heartbeat regions
#
clean_heartbeat_o2cb()
{
    if [ "$#" -lt "1" -o -z "$1" ]
    then
        echo "clean_heartbeat_o2cb(): Requires an argument" >&2
        return 1
    fi
    CLUSTER="$1"

    if [ ! -f "$(configfs_path)/cluster/${CLUSTER}/heartbeat/*" ]
    then
        return
    fi

    echo -n "Cleaning heartbeat on cluster \"${CLUSTER}\": "

    ls -1 "$(configfs_path)/cluster/${CLUSTER}/heartbeat/" | while read HBUUID
    do
        if [ ! -d "$(configfs_path)/cluster/${CLUSTER}/heartbeat/${HBUUID}" ]
        then
            continue
        fi

        OUTPUT="`ocfs2_hb_ctl -I -u ${HBUUID} 2>&1`"
        if [ $? != 0 ]
        then
            echo "Failed"
            echo "${OUTPUT}" >&2
            exit 1
        fi

        REF="`echo ${OUTPUT} | awk '/refs/ {print $2; exit;}' 2>&1`"
        if [ $REF != 0 ]
        then
           echo "Failed"
           echo "At least one heartbeat region still active" >&2
           exit 1
        else
           OUTPUT="`ocfs2_hb_ctl -K -u ${HBUUID} 2>&1`"
        fi
    done
    if [ $? = 1 ]
    then
        exit 1
    fi
    echo "OK"
}

#
# clean_cluster_o2cb()
# Force cleans configured cluster
#
# 0 is clean, 1 is error, 2 is not clean
#
clean_cluster_o2cb()
{
    if [ "$#" -lt "1" -o -z "$1" ]
    then
        echo "clean_cluster_o2cb(): Requires an argument" >&2
        return 1
    fi
    CLUSTER="$1"

    if [ -d "$(configfs_path)/cluster/${CLUSTER}/node/" ]
    then
        ls -1 "$(configfs_path)/cluster/${CLUSTER}/node/" | while read NODE
        do
            rmdir "$(configfs_path)/cluster/${CLUSTER}/node/${NODE}" >/dev/null 2>&1
        done
    fi

    if [ -d "$(configfs_path)/cluster/${CLUSTER}" ]
    then
        rmdir "$(configfs_path)/cluster/${CLUSTER}" >/dev/null 2>&1
    fi

    if [ -d "$(configfs_path)/cluster/${CLUSTER}" ]
    then
        return 2
    fi

    return 0
}

select_stack_plugin()
{
    case "$O2CB_STACK" in
    o2cb)
        echo o2cb
        ;;
    ????)
        echo user
        ;;
    *)
        echo -n "Checking if cluster stack \"$O2CB_STACK\" is supported: "
        if_fail 1 "Invalid cluster stack name \"$O2CB_STACK\""
        ;;
    esac
 }

#
# load_stack_o2cb()
# Load the o2cb stack.
#
# 0 is success, 1 is error, 2 is already loaded
#
load_stack_o2cb()
{
    PLUGIN="o2cb"
    PLUGIN_MODULE="ocfs2_stack_${PLUGIN}"

    #
    # Older drivers don't have stack plugins.  They only support the
    # classic stack.  It is loaded in ocfs2_nodemanager, which is pulled
    # in during the load of ocfs2_dlmfs.  Thus, if we are using the classic
    # stack and cannot load the stack plugin, we silently let the later
    # code try for older drivers.
    #
    # For a newer driver, if ocfs2_stackglue is loaded, we'll see
    # CLUSTER_STACK_FILE.  Thus, we can determine what mode we're in.
    #
    [ ! -e "$CLUSTER_STACK_FILE" ] && \
        modprobe -s ocfs2_stackglue

    # If we're a newer driver, CLUSTER_STACK_FILE will now appear
    if [ -e "$CLUSTER_STACK_FILE" ]
    then
        SP_OUT="$(awk '/^'$PLUGIN'$/{print; exit}' "$LOADED_PLUGINS_FILE" 2>/dev/null)"
        if [ -z "$SP_OUT" ]
        then
            echo -n "Loading stack plugin \"$PLUGIN\": "
            modprobe -s "$PLUGIN_MODULE"
            [ "$?" != 0 ] && if_fail 1 "Unable to load module \"$PLUGIN_MODULE\""
            if_fail 0
        fi
    fi

    mount_filesystem "ocfs2_dlmfs" "/dlm"
    if_fail $?

    return 0
}

#
# dlmfs_user_capable()
# Check if dlmfs supports user stacks.
#
# 0 is yes, 1 is no.
#
dlmfs_user_capable()
{
    if [ -e "$DLMFS_CAPABILITIES_FILE" ]
    then
        CAP_LINE="$(awk '/\ystackglue\y/{print}' "$DLMFS_CAPABILITIES_FILE" 2>/dev/null)"
    else
        CAP_LINE="$(modinfo ocfs2_dlmfs 2>/dev/null |
                        awk '/parm:[ \t]*capabilities:.*\ystackglue\y/{
                                print
                            }')"
    fi

    test x"$CAP_LINE" != x
}

#
# load_stack_user()
# Load the userspace stack plugin.
#
# 0 is success, 1 is error, 2 is already loaded
#
load_stack_user()
{
    PLUGIN="user"
    PLUGIN_MODULE="ocfs2_stack_${PLUGIN}"

    #
    # Older drivers don't have stack plugins.  They only support the
    # classic stack.  It is loaded in ocfs2_nodemanager, which is pulled
    # in during the load of ocfs2_dlmfs.  Thus, if we are using the classic
    # stack and cannot load the stack plugin, we silently let the later
    # code try for older drivers.
    #
    # For a newer driver, if ocfs2_stackglue is loaded, we'll see
    # CLUSTER_STACK_FILE.  Thus, we can determine what mode we're in.
    #
    [ ! -e "$CLUSTER_STACK_FILE" ] && \
        modprobe -s ocfs2_stackglue

    if [ ! -e "$CLUSTER_STACK_FILE" ]
    then
        echo -n "Checking if stack \"$O2CB_STACK\" is supported: "
        if_fail 1 "User stack plugin is not supported"
    fi

    SP_OUT="$(awk '/^'$PLUGIN'$/{print; exit}' "$LOADED_PLUGINS_FILE" 2>/dev/null)"
    if [ -z "$SP_OUT" ]
    then
        echo -n "Loading stack plugin \"$PLUGIN\": "
        modprobe -s "$PLUGIN_MODULE"
        [ "$?" != 0 ] && if_fail 1 "Unable to load module \"$PLUGIN_MODULE\""
        if_fail 0
    fi

    if dlmfs_user_capable
    then
        mount_filesystem "ocfs2_dlmfs" "/dlm"
        if_fail $?
    fi

    return 0
}

#
# unload_stack_plugins()
# Since we can have both plugins unloaded, we just do the same work
# for all stack types
#
unload_stack_plugins()
{
    # Whether not loaded or old drivers, if we can't find the stack glue,
    # we have nothing to do.
    [ ! -e "$LOADED_PLUGINS_FILE" ] && return 2

    while read plugin
    do
        unload_module "ocfs2_stack_${plugin}"
        if_fail $? "Unable to unload ocfs2_stack_${plugin}"
    done <"$LOADED_PLUGINS_FILE"

    unload_module "ocfs2_stackglue"
    if_fail $? "Unable to unload ocfs2_stackglue"
}

#
#
# unload_stack_o2cb()
# Unload the o2cb stack plugin
#
# 0 is success, 1 is error, 2 is not loaded.
#
unload_stack_o2cb()
{
    PLUGIN="o2cb"
    PLUGIN_MODULE="ocfs2_stack_${PLUGIN}"

    if [ -d "$(configfs_path)/cluster/" ]
    then
        ls -1 $(configfs_path)/cluster/ | while read CLUSTER
        do
            if [ ! -L "$(configfs_path)/cluster/${CLUSTER}" -a \
                 -d "$(configfs_path)/cluster/${CLUSTER}" ]
            then
                echo "Unable to unload modules as O2CB cluster ${CLUSTER} is still online" >&2
                exit 1
            fi
        done
        if [ $? = 1 ]
        then
            exit 1
        fi
    fi

    unmount_filesystem "ocfs2_dlmfs" "/dlm"
    if_fail $?

    unload_stack_plugins
}

#
# unload_stack_user()
# Unload the user stack plugin
#
# 0 is success, 1 is error, 2 is not loaded.
#
unload_stack_user()
{
    PLUGIN="$(select_stack_plugin)"
    PLUGIN_MODULE="ocfs2_stack_${PLUGIN}"

    if status_daemon >/dev/null 2>&1
    then
        echo "Unable to unload modules as the cluster is still online" >&2
        exit 1
    fi

    unmount_filesystem "ocfs2_dlmfs" "/dlm"
    if_fail $?

    unload_stack_plugins
}

status_stack_plugin()
{
    PLUGIN="$(select_stack_plugin)"

    if [ ! -e "$LOADED_PLUGINS_FILE" ]
    then
        # The old stack may be fine here.
        [ "$PLUGIN" = "o2cb" ] && return

        echo "Stack glue driver: Not loaded"
        return
    fi
    echo "Stack glue driver: Loaded"

    echo -n "Stack plugin \"$PLUGIN\": "
    if grep "$PLUGIN" "$LOADED_PLUGINS_FILE" >/dev/null 2>&1
    then
        echo "Loaded"
    else
        echo "Not loaded"
    fi
}

status_stack_o2cb()
{
    status_stack_plugin
    status_filesystem "ocfs2_dlmfs" "/dlm"
}

status_stack_user()
{
    status_stack_plugin

    echo -n "Driver for \"ocfs2\": "
    if driver_filesystem "ocfs2"
    then
        echo "Loaded"
    else
        echo "Not loaded"
    fi
 }

#
# unload_module()
# Unload a module
#
# 0 is success, 1 is error, 2 is not loaded
#
unload_module()
{
    if [ "$#" -lt "1" -o -z "$1" ]
    then
        echo "unload_module(): Requires an argument" >&2
        return 1
    fi
    MODNAME="$1"

    MODOUT="`awk '$1 ~ /^'$MODNAME'$/{print $1,$3;exit}' < /proc/modules 2>/dev/null`"
    if [ -z "$MODOUT" ]
    then
        return 2
    fi
    case "$MODOUT" in
    $MODNAME\ 0)
        ;;
    $MODNAME\ *)
        return 2
        ;;
    *)
        echo -n "Invalid module parsing! "
        return 1
        ;;
    esac

    echo -n "Unloading module \"$MODNAME\": "
    modprobe -rs "$MODNAME"
    if [ "$?" != 0 ]
    then
        echo "Unable to unload module \"$MODNAME\"" >&2
        return 1
    fi

    return 0
}

#
# check_load_module()
#
# 0 is not loaded, 1 is error, 2 is already loaded
#
check_load_module()
{
    if [ "$#" -lt "1" -o -z "$1" ]
    then
        echo "check_load_module(): Requires an argument" >&2
        return 1
    fi
    MODNAME="$1"

    echo -n "Module \"$MODNAME\": "
    MODOUT="`awk '$1 ~ /^'$MODNAME'$/{print $1,$3;exit}' < /proc/modules 2>/dev/null`"
    if [ -z "$MODOUT" ]
    then
        echo "Not loaded"
        return 0
    fi
    echo "Loaded"
    return 2
}

load()
{
    PLUGIN="$(select_stack_plugin)"

    # XXX: SPECIAL CASE!  We must load configfs for configfs_path() to work
    load_filesystem "configfs"
    if_fail $?

    mount_filesystem "configfs" "$(configfs_path)"
    if_fail $?

    load_stack_$PLUGIN

    return 0
}

load_status()
{
    PLUGIN="$(select_stack_plugin)"
    status_filesystem "configfs" "$(configfs_path)"
    status_stack_$PLUGIN

    return 0
}

register_cluster_o2cb()
{
    if [ "$#" -lt "1" -o -z "$1" ]
    then
        echo "register_cluster_o2cb(): Requires an argument" >&2
        return 1
    fi
    CLUSTER="$1"

    if ! [ -f ${CLUSTERCONF} ]
    then
        echo -n "Checking O2CB cluster configuration : "
        if_fail 1
    fi

    echo -n "Registering O2CB cluster \"${CLUSTER}\": "
    OUTPUT=$(o2cb register-cluster "${CLUSTER}" 2>&1)
    if [ $? = 0 ]
    then
        echo "OK"
        return 0
    else
        echo "Failed"
        echo "$OUTPUT"
    fi

    return 1
}

unregister_cluster_o2cb()
{
    if [ "$#" -lt "1" -o -z "$1" ]
    then
        echo "unregister_cluster_o2cb(): Requires an argument" >&2
        return 1
    fi
    CLUSTER="$1"

    echo -n "Unregistering O2CB cluster \"${CLUSTER}\": "
    OUTPUT=$(o2cb unregister-cluster "${CLUSTER}" 2>&1)
    if [ $? = 0 ]
    then
        echo "OK"
        return 0
    else
        echo "Failed"
        echo "$OUTPUT"
    fi

    return 1
}

#
# check_register_o2cb()
#
# 0 is not registered, 1 is error, 2 is registered
#
check_register_o2cb()
{
    if [ "$#" -lt "1" -o -z "$1" ]
    then
        echo "check_register_o2cb(): Requires an argument" >&2
        return 1
    fi
    CLUSTER="$1"

    RC=0
    if [ -d "$(configfs_path)/cluster/${CLUSTER}/node/" ]
    then
        ls -1 "$(configfs_path)/cluster/${CLUSTER}/node/" | while read NODE
        do
            LOCAL="`cat \"$(configfs_path)/cluster/${CLUSTER}/node/${NODE}/local\"`"
            if [ $LOCAL = 1 ]
            then
                return 2
            fi
        done
        if [ $? = 2 ]
        then
            RC=2
        fi
    fi
    return $RC
}

start_global_heartbeat_o2cb()
{
    if [ "$#" -lt "1" -o -z "$1" ]
    then
        echo "start_global_heartbeat_o2cb(): Requires an argument" >&2
        return 1
    fi
    CLUSTER="$1"

    if ! [ -f ${CLUSTERCONF} ]
    then
        echo -n "Checking O2CB cluster configuration : "
        if_fail 1
    fi

    global_heartbeat_enabled_o2cb
    if [ $? -ne 1 ]
    then
        return 0;
    fi

    echo -n "Starting global heartbeat for cluster \"${CLUSTER}\": "
    OUTPUT=$(o2cb start-heartbeat "${CLUSTER}" 2>&1)
    if [ $? = 0 ]
    then
        echo "OK"
        return 0
    else
        echo "Failed"
        echo "$OUTPUT"
    fi

    return 1
}

stop_global_heartbeat_o2cb()
{
    if [ "$#" -lt "1" -o -z "$1" ]
    then
        echo "stop_global_heartbeat_o2cb(): Requires an argument" >&2
        return 1
    fi
    CLUSTER="$1"

    global_heartbeat_enabled_o2cb
    if [ $? -ne 1 ]
    then
        return 0;
    fi

    echo -n "Stopping global heartbeat on cluster \"${CLUSTER}\": "
    OUTPUT=$(o2cb stop-heartbeat "${CLUSTER}" 2>&1)
    if [ $? = 0 ]
    then
        echo "OK"
        return 0
    else
        echo "Failed"
        echo "$OUTPUT"
    fi

    return 1
}

online_o2cb()
{
    if [ "$#" -lt "1" -o -z "$1" ]
    then
        echo "online_o2cb(): Requires an argument" >&2
        return 1
    fi
    CLUSTER="$1"

    # allow re-online if global heartbeat enabled
    check_online $CLUSTER
    if [ $? = 2 ]
    then
        global_heartbeat_enabled_o2cb
        if [ $? -ne 1 ]
        then
            echo "Cluster ${CLUSTER} already online"
            return 1
        fi
    fi

    register_cluster_o2cb "${CLUSTER}"
    if [ $? -eq 1 ]
    then
        unregister_cluster_o2cb "${CLUSTER}"
        return 1
    fi

    echo -n "Setting O2CB cluster timeouts : "
    set_timeouts_o2cb
    echo "OK"

    start_global_heartbeat_o2cb "${CLUSTER}"
    if [ $? -eq 1 ]
    then
        stop_global_heartbeat_o2cb "${CLUSTER}"
        return 1
    fi

    o2hbmonitor 2>/dev/null

    return 0
}

online_user()
{
    if [ "$#" -lt "1" -o -z "$1" ]
    then
        echo "online_user(): Requires an argument" >&2
        return 1
    fi
    CLUSTER="$1"

    check_online $CLUSTER
    if [ $? = 2 ]
    then
        echo "Cluster ${CLUSTER} already online"
        return 1
    fi

    load_filesystem "ocfs2"
    if_fail $? "Unable to load ocfs2 driver"

    bringup_daemon
    if_fail $? "Unable to start control daemon"
}

online()
{
    CLUSTER="${1:-${O2CB_BOOTCLUSTER}}"
    if [ -z "$CLUSTER" ]
    then
        echo "Cluster not known"
        return
    fi
    PLUGIN="$(select_stack_plugin)"

    if [ -f "$CLUSTER_STACK_FILE" ]
    then
        echo -n "Setting cluster stack \"$O2CB_STACK\": "
        echo "$O2CB_STACK" >"$CLUSTER_STACK_FILE" 2>/dev/null
        if_fail $? "Unable to store cluster stack \"$O2CB_STACK\""
    elif [ "$O2CB_STACK" != "o2cb" ]
    then
        echo -n "Setting cluster stack \"$O2CB_STACK\": "
        if_fail 1 "Stack \"$O2CB_STACK\" is not supported"
    fi

    online_$PLUGIN "$CLUSTER"
}

#
# check_online_o2cb()
#
# 0 is not online, 1 is error, 2 is online
#
check_online_o2cb()
{
    if [ "$#" -lt "1" -o -z "$1" ]
    then
        echo "check_online_o2cb(): Requires an argument" >&2
        return 1
    fi
    CLUSTER="$1"

    check_register_o2cb "$CLUSTER"
    if [ $? -ne 2 ]
    then
         return $RC
    fi

    global_heartbeat_enabled_o2cb "$CLUSTER"
    if [ $? -eq 0 ]
    then
        return 2
    fi

    check_heartbeat_o2cb "$CLUSTER"
    return $?
}

#
# check_online_user()
#
# 0 is not online, 1 is error, 2 is online
#
check_online_user()
{
    if status_daemon >/dev/null 2>&1
    then
        return 2
    else
        return 1
    fi
}

#
# check_online()
#
# 0 is not online, 1 is error, 2 is online
#
check_online()
{
    PLUGIN="$(select_stack_plugin)"

    check_online_$PLUGIN $1
}

offline_o2cb()
{
    if [ "$#" -lt "2" -o -z "$1" -o -z "$2" ]
    then
        echo "offline_o2cb(): Missing arguments" >&2
        return 1
    fi
    CLUSTER="$1"
    FORCE="$2"

    if [ ! -e "$(configfs_path)/cluster/${CLUSTER}" ]
    then
        return
    fi

    stop_global_heartbeat_o2cb "$CLUSTER"

    clean_heartbeat_o2cb $CLUSTER

    echo -n "Stopping O2CB cluster ${CLUSTER}: "
    check_heartbeat_o2cb $CLUSTER
    if [ $? != 0 ]
    then
        echo "Failed"
        echo "Unable to stop cluster as heartbeat region still active" >&2
        exit 1
    fi

    killall -e o2hbmonitor 2>/dev/null

    # ignoring force-offline as this should be enough

    unregister_cluster_o2cb "${CLUSTER}"
    return $?
}

offline_user()
{
    if [ "$#" -lt "2" -o -z "$1" -o -z "$2" ]
    then
        echo "offline_user(): Missing arguments" >&2
        return 1
    fi
    CLUSTER="$1"
    FORCE="$2"

    kill_daemon
    [ $? = 1 -a "$FORCE" -eq 1 ] && kill_daemon -9
}

offline()
{
    CLUSTER="${1:-${O2CB_BOOTCLUSTER}}"
    if [ -z "$CLUSTER" ]
    then
        return
    fi
    PLUGIN="$(select_stack_plugin)"

    if [ $# -gt 1 ]
    then
        FORCE=$2
    else
        FORCE=0
    fi

    offline_$PLUGIN "$CLUSTER" "$FORCE"

    unload_filesystem "ocfs2"
    if_fail "$?"
}

start()
{
    if [ "$O2CB_ENABLED" != "true" ]
    then
        exit 0
    fi

    load
    online "$1"
}

unload()
{
    PLUGIN="$(select_stack_plugin)"

    unload_stack_$PLUGIN

    # Only unmount configfs if there are no other users
    if [ -z "$(ls -1 "$(configfs_path)" 2>/dev/null)" ]
    then
        unmount_filesystem "configfs" "$(configfs_path)"
        if_fail $?
    fi
}

stop()
{
    offline "$1"
    unload
}

configure()
{
    configure_ask
    if [ -z "$DPKGRECONF" ]; then
	write_sysconfig
	if_fail "$?" "Unable to write the driver configuration"
    fi
}

online_status_o2cb()
{
    if [ "$#" -lt "1" -o -z "$1" ]
    then
        echo "online_status_o2cb(): Missing arguments" >&2
        return 1
    fi
    CLUSTER="$1"

    echo -n "Checking O2CB cluster \"$CLUSTER\": "
    check_online_o2cb $CLUSTER
    if [ $? = 2 ]
    then
       echo "Online"
    else
       echo "Offline"
       return 0;
    fi

    show_timeouts_o2cb

    HBMODE="Local"
    global_heartbeat_enabled_o2cb "$CLUSTER"
    if [ $? -eq 1 ]
    then
        HBMODE="Global"
    fi

    echo "  Heartbeat mode: ${HBMODE}"

    echo -n "Checking O2CB heartbeat: "
    check_heartbeat_o2cb $CLUSTER
    if [ $? -eq 2 ]
    then
        echo "Active"
    else
        echo "Not active"
        return 0;
    fi

    if [ $HBMODE = "Global" ]
    then
        list_heartbeat_o2cb $CLUSTER
        list_nodes_o2cb
    fi
}

online_status_user()
{
    status_daemon
}

online_status()
{
    if [ "$#" -lt "1" -o -z "$1" ]
    then
        echo "online_status(): Missing arguments" >&2
        return 1
    fi
    CLUSTER="$1"
    PLUGIN="$(select_stack_plugin)"

    online_status_$PLUGIN "$CLUSTER"
}

status()
{
    load_status

    CLUSTER="${1:-${O2CB_BOOTCLUSTER}}"
    if [ -z "$CLUSTER" ]
    then
        return 1;
    fi

    online_status "$CLUSTER"
}



case "$1" in
    start)
        start "$2"
        ;;

    status)
        status "$2"
        ;;

    stop)
        stop "$2"
        ;;

    restart)
        stop "$2"
        start "$2"
        ;;

    force-reload)
        stop "$2"
        start "$2"
        ;;

    load)
        load
        ;;

    online)
        load
        online "$2"
        ;;

    offline)
        offline "$2"
        ;;

    force-offline)
        offline "$2" 1
        ;;

    unload)
        offline "$2"
        unload
        ;;

    configure)
        configure
        if [ "$O2CB_ENABLED" = "true" ]
        then
            start
        else
            stop
        fi
        ;;

    enable)
        O2CB_ENABLED=true
        write_sysconfig
        if_fail "$?" "Unable to write the driver configuration"
        start
        ;;

    disable)
        O2CB_ENABLED=false
        write_sysconfig
        if_fail "$?" "Unable to write the driver configuration"
        stop
        ;;

    *)
        echo "Usage: $0 {start|stop|restart|force-reload|enable|disable|configure|load|unload|online|offline|force-offline|status}"
        exit 1
        ;;
esac

exit 0
