#!/bin/bash
# init fragment for O2CB.
#
# chkconfig: 2345 24 20
# 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:
# Description: Load O2CB cluster services at system boot.
### END INIT INFO

# Force LC_ALL=C
export LC_ALL=C

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
elif [ -d /etc/sysconfig ]
then
    CONFIGURATION=/etc/sysconfig/o2cb
else
    CONFIGURATION=/etc/default/o2cb
fi

# Source configuration, 
[ -f "${CONFIGURATION}" ] && . "${CONFIGURATION}"

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
}

# 
# This is a tricky bit of eval work.  There are many steps involved in
# O2CB startup/shutdown, so we collect them here.  Each line is a line
# of shell code that needs to be run.  The code is run via eval as
# follows:
#
# start/load:
#    Eval of the exact lines, in order.  So, the first call is
#    "eval load_module configfs".
#
# status:
#    Eval of the lines with "check_" prepended, in order.  So the first
#    call is "eval check_load_module configfs".
#
# stop/unload:
#    Eval of the lines with "un" prepened, in reverse order.  So the
#    *last* call is "eval unload_module configfs".
# 
# To provide an action, create a set of shell functions or commands
# "foo", "check_foo", and "unfoo".  Then add "foo arguments" to the
# appropriate place.  Be careful, eval requires quoting to be right.
#
LOAD_ACTIONS=(#"load_module configfs"
		"mount_fs configfs "'$(configfs_path)'
		#"load_module sctp"
		#"load_module ocfs2_nodemanager"
		#"load_module ocfs2_dlm"
		#"load_module ocfs2_dlmfs"
		"mount_fs ocfs2_dlmfs /dlm")


#
# 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.
# The preferred method is running 'dpkg-reconfigure ocfs2-tools'.
# Please use that last method to modify this file.
#

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

# 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}

EOF

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

#
# configure_ask()
#
# Ask configuration questions, setting the shell vars.
#
configure_ask()
{
    cat <<EOF
Configuring the O2CB driver.

Please configure this service in the Debian way:

 dpkg-reconfigure ocfs2-tools

EOF

exit 0
# This is not intended to run beyond this point.

    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 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

	# 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
}


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

    MODOUT="`awk '$1 ~ /^'$MODNAME'$/{print $1;exit}' < /proc/modules 2>/dev/null`"
    if [ -n "$MODOUT" ]
    then
        return 2
    fi

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

    return 0
}

#
# check_heartbeat()
#
# 0 is hb not active, 1 is error, 2 is hb active
#
check_heartbeat()
{
    if [ "$#" -lt "1" -o -z "$1" ]
    then
        echo "check_heartbeat(): 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()
# Removes the inactive heartbeat regions
# 
clean_heartbeat()
{
    if [ "$#" -lt "1" -o -z "$1" ]
    then
        echo "clean_heartbeat(): Requires an argument" >&2
        return 1
    fi
    CLUSTER="$1"

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

    if [ ! -d "$(configfs_path)/cluster/${CLUSTER}/heartbeat/" ]
    then
        echo "OK"
        return
    fi

    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"
}

#
# 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
}


#
# mount_fs()
# Mount a filesystem.
#
# 0 is success, 1 is error, 2 is already mounted
#
mount_fs()
{
    TYPE="$1"
    FULL_MOUNT="$2"
    FULL_MOUNTSEARCH="`echo "$FULL_MOUNT" | sed -e 's/\//\\\\\//g'`"
    MOUNTOUT="`awk '$2 ~ /^'$FULL_MOUNTSEARCH'$/{print $2; exit}' < /proc/mounts 2>/dev/null`"

    if [ -n "$MOUNTOUT" ]
    then
        return 2
    fi

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

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

    return 0
}

#
# check_mount_fs()
#
# 0 is not mounted, 1 is error, 2 is already mounted
#
check_mount_fs()
{
    TYPE="$1"
    FULL_MOUNT="$2"
    FULL_MOUNTSEARCH="`echo "$FULL_MOUNT" | sed -e 's/\//\\\\\//g'`"
    MOUNTOUT="`awk '$2 ~ /^'$FULL_MOUNTSEARCH'$/{print $2; exit}' < /proc/mounts 2>/dev/null`"

    echo -n "Filesystem \"$TYPE\": "
    if [ -n "$MOUNTOUT" ]
    then
        echo "Mounted"
        return 2
    fi
    echo "Not mounted"
    return 0
}

#
# unmount_fs()
# Unmount a filesystem
#
# 0 is success, 1 is error, 2 is not mounted
#
unmount_fs()
{
    TYPE="$1"
    FULL_MOUNT="$2"
    FULL_MOUNTSEARCH="`echo "$FULL_MOUNT" | sed -e 's/\//\\\\\//g'`"
    MOUNTOUT="`awk '$2 ~ /^'$FULL_MOUNTSEARCH'$/{print $2; exit}' < /proc/mounts 2>/dev/null`"

    if [ -z "$MOUNTOUT" ]
    then
        return 2
    fi

    echo -n "Unmounting ${TYPE} filesystem: "
    umount $FULL_MOUNT
    if [ $? != 0 ]
    then
        echo "Unable to unmount ${TYPE} filesystem" >&2
        return 1
    fi

    return 0
}

load()
{
    for i in $(seq 0 $((${#LOAD_ACTIONS[*]} - 1)) ); do
        eval ${LOAD_ACTIONS[i]}
        if_fail "$?"
    done
}

load_status()
{
    for i in $(seq 0 $((${#LOAD_ACTIONS[*]} - 1)) ); do
        eval "check_${LOAD_ACTIONS[i]}"
    done
    return "$?"
}

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

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

    echo -n "Starting Oracle cluster ${CLUSTER}: "
    OUTPUT="`o2cb_ctl -H -n "${CLUSTER}" -t cluster -a online=yes 2>&1`"
    if [ $? = 0 ]
    then
        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"
            else
                echo "WARNING: Unable to set heartbeat dead threshold" >&2
            fi
        fi

        echo "OK"
        return
    else
        echo "Failed"
        echo "$OUTPUT"
    fi

    echo -n "Stopping Oracle cluster ${CLUSTER}: "
    OUTPUT="`o2cb_ctl -H -n "${CLUSTER}" -t cluster -a online=no 2>&1`"
    if_fail "$?" "$OUTPUT"
}

#
# check_online()
#
# 0 is not online, 1 is error, 2 is online
#
check_online()
{
    if [ "$#" -lt "1" -o -z "$1" ]
    then
        echo "check_online(): 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
}

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

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

    clean_heartbeat $CLUSTER

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

    OUTPUT="`o2cb_ctl -H -n "${CLUSTER}" -t cluster -a online=no 2>&1`"
    if_fail "$?" "$OUTPUT"

    unload_module ocfs2
    if_fail "$?"
}

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

    load
    online "$1"
}

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

    for i in $(seq $((${#LOAD_ACTIONS[*]} - 1)) -1 0); do
        eval "un${LOAD_ACTIONS[i]}"
        if_fail "$?"
    done
}

stop()
{
    offline "$1"
    unload
}

configure()
{
    configure_ask
    #write_sysconfig
    #if_fail "$?" "Unable to write the driver configuration"
}

status()
{
    load_status
    if [ $? != 2 ]
    then
        return 0;
    fi

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

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

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

    return

    echo -n "Checking if O2CB is loaded: "
    RC=0
    for MODSPEC in $MODULE_LIST
    do
        MODULE_NAME="`echo $MODSPEC | cut -d: -f1`"
        FSTYPE="`echo $MODSPEC | cut -d: -f2`"
        MOUNT_POINT="`echo $MODSPEC | cut -d: -f3`"

        if grep "^${MODULE_NAME} " /proc/modules >/dev/null 2>&1
        then
            echo -n "${MODULE_NAME} "
        else
            RC=1
            break
        fi
    done
    if_fail "$RC"

    echo -n "Checking O2CB mount points: "
    for MODSPEC in $MODULE_LIST
    do
        MODULE_NAME="`echo $MODSPEC | cut -d: -f1`"
        FSTYPE="`echo $MODSPEC | cut -d: -f2`"
        MOUNT_POINT="`echo $MODSPEC | cut -d: -f3`"

        if [ -z "$FSTYPE" -o -z "$MOUNT_POINT" ]
        then
            continue
        fi

        FULL_MOUNT="${O2CB_MANAGER}/${MOUNT_POINT}"
        FULL_MOUNTSEARCH="`echo "$FULL_MOUNT" | sed -e 's/\//\\\\\//g'`"
        if grep "^${FSTYPE} ${FULL_MOUNTSEARCH} ${FSTYPE}" /proc/mounts >/dev/null 2>&1
        then
            echo -n "${MOUNT_POINT} "
        else
            RC=1
            break
        fi
    done
    if_fail "$RC"
}



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"
        ;;

    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|status}"
        exit 1
        ;;
esac

exit 0
