#!/bin/bash
# Copyright (c) 2005,2008 Oracle 
# Copyright (c) 2008 Andrew Beekhof
#                    All Rights Reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it would be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# Further, this software is distributed without any warranty that it is
# free of the rightful claim of any third person regarding infringement
# or the like.  Any license provided herein, whether implied or
# otherwise, applies only to this software file.  Patent licenses, if
# any, provided herein do not apply to combinations of this program with
# other software, or any other product whatsoever.
#
#######################################################################

: ${OCF_FUNCTIONS=${OCF_ROOT}/resource.d/heartbeat/.ocf-shellfuncs}
. ${OCF_FUNCTIONS}
: ${__OCF_ACTION=$1}

: ${OCF_RESKEY_stack:="pcmk"}
: ${OCF_RESKEY_sysfs:="/sys/fs"}
: ${OCF_RESKEY_configfs:="/sys/kernel/config"}
: ${OCF_RESKEY_daemon_timeout:="10"} # How long to wait for things to start
: ${OCF_RESKEY_CRM_meta_globally_unique:="false"}

DAEMON="/usr/sbin/ocfs2_controld.${OCF_RESKEY_stack}"
CLUSTER_STACK_FILE="${OCF_RESKEY_sysfs}/ocfs2/cluster_stack"
LOADED_PLUGINS_FILE="${OCF_RESKEY_sysfs}/ocfs2/loaded_cluster_plugins"

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

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

    test -n "$FSOUT"
    return $?
}

#
# 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
        ocf_log err "check_filesystem(): Missing arguments"
        exit 4
    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 $?
}

#
# 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 unloaded.
#
unload_filesystem()
{
    if [ "$#" != "1" -o -z "$1" ]
    then
        ocf_log err "unload_filesystem(): Missing an argument"
        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 0
    fi

    case "$MODOUT" in
    $FSNAME\ 0)
        ;;
    $FSNAME\ *)
        # The driver is busy, leave it alone
        ocf_log err "Module $FSNAME is still in use"
        return 1
        ;;
    *)
        ocf_log err "Invalid module parsing! "
        return 1
        ;;
    esac

    modprobe -rs "$FSNAME"
    if [ "$?" != 0 ]; then
        ocf_log err "Unable to unload module: $FSNAME"
        return 1
    fi

    return 0
}

status_daemon()
{
    PID=`pidof "$DAEMON"`
    if [ -n "$PID" ]; then
	return $OCF_SUCCESS
    fi
    return $OCF_NOT_RUNNING
}

bringup_daemon()
{
    if [ ! -e "$DAEMON" ]; then
	ocf_log err "Required binary not found: $DAEMON"
	return $OCF_ERR_INSTALLED
    fi

    "$DAEMON"; rc=$?
    if [ $rc != 0 ]; then
	ocf_log err "Could not start $DAEMON"
	return $OCF_ERR_GENERIC
    fi

    sleep 1
    COUNT=0
    rc=$OCF_NOT_RUNNING

    while [ $rc = $OCF_NOT_RUNNING ]; do
        COUNT=`expr $COUNT + 1`
        if [ $COUNT -gt $OCF_RESKEY_daemon_timeout ]; then
	    ocf_log err "`basename $DAEMON` did not come up"
	    return $OCF_ERR_GENERIC
        fi
	status_daemon; rc=$?
        sleep 1
    done

    return $rc
}

kill_daemon()
{
    status_daemon; rc=$?
    if [ $rc != $OCF_SUCCESS ]; then
	return $rc
    fi

    ocf_log info "Stopping `basename "$DAEMON"`"
    killproc "$DAEMON"

    while [ $rc = $OCF_NOT_RUNNING ]; do
        sleep 1
	status_daemon; rc=$?
    done

    return $OCF_SUCCESS
}

#
# Unload a module
# 0 is success, 1 is error, 2 is not loaded
#
unload_module()
{
    if [ "$#" -lt "1" -o -z "$1" ]
    then
	ocf_log err  "unload_module(): Requires an argument"
        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
        ;;
    *)
	ocf_log err "Invalid module parsing!"
        return 1
        ;;
    esac

    modprobe -rs "$MODNAME"
    if [ "$?" != 0 ]; then
	ocf_log err "Unable to unload module \"$MODNAME\""
        return 1
    fi

    return 0
}

o2cb_start() {

    o2cb_monitor; rc=$?
    if [ $rc != $OCF_NOT_RUNNING ]; then 
	return $rc
    fi

    ocf_log info "Starting $OCF_RESOURCE_INSTANCE"

    if [ ! -e "$CLUSTER_STACK_FILE" ]; then
	modprobe -s ocfs2_stackglue
	if [ $? != 0 ]; then
	    ocf_log err "Could not load ocfs2_stackglue"
	    return $OCF_ERR_INSTALLED
	fi
    fi

    SP_OUT="$(awk '/^'user'$/{print; exit}' "$LOADED_PLUGINS_FILE" 2>/dev/null)"
    if [ -z "$SP_OUT" ]
    then
        modprobe -s ocfs2_stack_user
	if [ $? != 0 ]; then
	    ocf_log err "Could not load ocfs2_stack_user"
	    return $OCF_ERR_INSTALLED
	fi
    fi

    SP_OUT="$(awk '/^'user'$/{print; exit}' "$LOADED_PLUGINS_FILE" 2>/dev/null)"
    if [ -z "$SP_OUT" ]; then
	ocf_log err "Switch to userspace stack unsuccessful"
	return $OCF_ERR_INSTALLED
    fi

    if [ -f "$CLUSTER_STACK_FILE" ]; then
        echo "$OCF_RESKEY_stack" >"$CLUSTER_STACK_FILE"
        if [ $? != 0 ]; then
	    ocf_log err "Userspace stack '$OCF_RESKEY_stack' not supported"
	    return $OCF_ERR_INSTALLED
	fi
    else
	ocf_log err "Switch to userspace stack not supported"
	return $OCF_ERR_INSTALLED
    fi

    driver_filesystem ocfs2; rc=$?
    if [ $rc != 0 ]; then
	modprobe -s ocfs2
	if [ "$?" != 0 ]; then
            ocf_log err "Unable to load ocfs2 module"
            return $OCF_ERR_INSTALLED
	fi
    fi

    bringup_daemon
    return $?
}

o2cb_stop() {
    o2cb_monitor; rc=$?
    case $rc in
	$OCF_NOT_RUNNING) return $OCF_SUCCESS;;
    esac

    ocf_log info "Stopping $OCF_RESOURCE_INSTANCE"

    kill_daemon
    if [ $? != 0 ]; then 
        ocf_log err "Unable to unload modules: the cluster is still online"
	return $OCF_ERR_GENERIC
    fi

    unload_filesystem ocfs2
    if [ $? = 1 ]; then
	ocf_log err "Unable to unload ocfs2 module"
	return $OCF_ERR_GENERIC
    fi

    # If we can't find the stack glue, we have nothing to do.
    [ ! -e "$LOADED_PLUGINS_FILE" ] && return $OCF_SUCCESS

    while read plugin
    do
        unload_module "ocfs2_stack_${plugin}"
        if [ $? = 1 ]; then
	    ocf_log err "Unable to unload ocfs2_stack_${plugin}"
	    return $OCF_ERR_GENERIC
	fi
    done <"$LOADED_PLUGINS_FILE"

    unload_module "ocfs2_stackglue"
    if [ $? = 1 ]; then
	ocf_log err "Unable to unload ocfs2_stackglue"
	return $OCF_ERR_GENERIC
    fi

    # Don't unmount configfs - it's always in use by libdlm
}

o2cb_monitor() {
    o2cb_validate

    # Assume that ocfs2_controld will terminate if any of the conditions below are met

    driver_filesystem configfs; rc=$?
    if [ $rc != 0 ]; then
	ocf_log info "configfs not loaded"
	return $OCF_NOT_RUNNING
    fi

    check_filesystem configfs "${OCF_RESKEY_configfs}"; rc=$?
    if [ $rc != 0 ]; then
	ocf_log info "configfs not mounted"
	return $OCF_NOT_RUNNING
    fi

    if [ ! -e "$LOADED_PLUGINS_FILE" ]; then
	ocf_log info "Stack glue driver not loaded"
	return $OCF_NOT_RUNNING
    fi

    grep user "$LOADED_PLUGINS_FILE" >/dev/null 2>&1; rc=$?
    if [ $rc != 0 ]; then
	ocf_log err "Wrong stack `cat $LOADED_PLUGINS_FILE`"
	return $OCF_ERR_INSTALLED
    fi

    driver_filesystem ocfs2; rc=$?
    if [ $rc != 0 ]; then
	ocf_log info "ocfs2 not loaded"
	return $OCF_NOT_RUNNING
    fi

    status_daemon
    return $?
}

o2cb_usage() {
    echo "usage: $0 {start|stop|monitor|validate-all|meta-data}"
    echo "  Expects to have a fully populated OCF RA-compliant environment set."
    echo "  In particualr, a value for OCF_ROOT"
}

o2cb_validate() {
    check_binary ${DAEMON}

    case ${OCF_RESKEY_CRM_meta_globally_unique} in
	yes|Yes|true|True|1) 
	    ocf_log err "$OCF_RESOURCE_INSTANCE must be configured with the globally_unique=false meta attribute"
	    exit $OCF_ERR_CONFIGURED
	    ;;
    esac

    return $OCF_SUCCESS
}

meta_data() {
	cat <<END
<?xml version="1.0"?>
<!DOCTYPE resource-agent SYSTEM "ra-api-1.dtd">
<resource-agent name="o2cb" version="1.0">
  <version>1.0</version>
  <shortdesc lang="en">OCFS2 daemon resource agent</shortdesc>
  <longdesc lang="en">
This Resource Agent controls the userspace daemon needed by OCFS2.
  </longdesc>
  <parameters>

    <parameter name="sysfs" unique="0">
      <longdesc lang="en">
Location where sysfs is mounted
      </longdesc>
      <shortdesc lang="en">Sysfs location</shortdesc>
      <content type="string" default="/sys/fs"/>
    </parameter>

    <parameter name="configfs" unique="0">
      <longdesc lang="en">
Location where configfs is mounted
      </longdesc>
      <shortdesc lang="en">Configfs location</shortdesc>
      <content type="string" default="/sys/kernel/config"/>
    </parameter>

    <parameter name="stack" unique="0">
      <longdesc lang="en">
Which userspace stack to use.  Known values: pcmk
      </longdesc>
      <shortdesc lang="en">Userspace stack</shortdesc>
      <content type="string" default="pcmk"/>
    </parameter>

    <parameter name="daemon_timeout" unique="0">
      <longdesc lang="en">
Number of seconds to allow the control daemon to come up
      </longdesc>
      <shortdesc lang="en">Daemon Timeout</shortdesc>
      <content type="string" default="10"/>
    </parameter>

  </parameters>
  <actions>
    <action name="start"         timeout="90" />
    <action name="stop"          timeout="100" />
    <action name="monitor"       timeout="20" depth="0"/>
    <action name="meta-data"     timeout="5" />
    <action name="validate-all"  timeout="30" />
  </actions>
</resource-agent>
END
}

case $__OCF_ACTION in
meta-data)	meta_data
		exit $OCF_SUCCESS
		;;
start)		o2cb_start
		;;
stop)		o2cb_stop
		;;
monitor)	o2cb_monitor
		;;
validate-all)	o2cb_validate
		;;
usage|help)	o2cb_usage
		exit $OCF_SUCCESS
		;;
*)		o2cb_usage
		exit $OCF_ERR_UNIMPLEMENTED
		;;
esac

exit $?
