#!/bin/sh

# This is an example CTDB NFS callout script for Ganesha.  It is based
# on the last version of 60.ganesha shipped with CTDB.  As such, it
# does not try to monitor RPC services that were not monitored by
# 60.ganesha - this might be a useful improvement.  It has also not
# been properly tested.

# You should check your version of NFS Ganesha to see if it ships with
# a newer callout.

# To use this:
#
# * Set CTDB_NFS_CALLOUT in your CTDB configuration to point to this
#   script
#
# * Rename the following files in nfs-checks.d so that they no longer
#   have the ".check" suffix:
#     * 10.status.check
#     * 20.nfs.check
#     * 30.nlockmgr.check
#     * 40.mountd.check
#     * 50.rquotad.check
#
# * Install 20.nfs-ganesha.check to nfs-checks.d/20.nfs.check
#
# * It is recommended, but not required, to install the grace_period
#   script (usually shipped in a utils package for NFS-Ganesha) to
#   /usr/bin/grace_period

# I (Martin Schwenke) hereby relicense all of my contributions to this
# callout (and, previously, to 60.ganesha) to a license compatible
# with NFS Ganesha (right now this is LGPLv3, but I'm flexible).
# There may be other contributions to be considered for relicensing,
# particularly those in commit 28cbe527d47822f870e8252495ab2a1c8fddd12f.

######################################################################

# Exit on 1st error
set -e

# Filesystem type and mount point for the (typically clustered)
# volume that will contain the NFS-Ganesha state.
state_fs="${CTDB_NFS_STATE_FS_TYPE:-gpfs}"
state_dir="${CTDB_NFS_STATE_MNT}" # No sane default.

# To change the following, edit the default values below.  Do not set
# these - they aren't configuration variables, just hooks for testing.
nfs_exports_file="${CTDB_NFS_EXPORTS_FILE:-/etc/ganesha/ganesha.conf}"
nfs_service="${CTDB_NFS_SERVICE:-nfs-ganesha}"
ganesha_rec_subdir=${CTDB_GANESHA_REC_SUBDIR:-.ganesha}
procfs=${PROCFS_PATH:-/proc}

case $state_fs in
    gpfs)
        GANRECDIR="/var/lib/nfs/ganesha"
        ;;
    glusterfs)
        if [ -z "${state_dir}" ]; then
            echo "CTDB_NFS_STATE_MNT not defined for GlusterFS"
            exit 1
        fi
        host=`hostname`
        NODESTATEDIR="$state_dir/nfs-ganesha/$host"
        GANSTATEDIR="$state_dir/nfs-ganesha/.noderefs"
        NODESTATELN="$GANSTATEDIR/$host"
        ;;
esac


##################################################

usage ()
{
    _c=$(basename $0)
    cat <<EOF
usage: $_c { shutdown | startup }
       $_c { stop | start | check } nfs
       $_c { releaseip | takeip }
       $_c { monitor-list-shares }
EOF
    exit 1
}


##################################################
# Basic service stop and start

basic_stop ()
{
    case "$1" in
	nfs)
	    service "$nfs_service" stop
	    ;;
	*)
	    usage
    esac
}

basic_start ()
{
    case "$1" in
	nfs)
	    service "$nfs_service" start
	    ;;
	*)
	    usage
    esac
}

##################################################
# "stop" and "start" options for restarting

service_stop ()
{
    case "$1" in
	nfs)
	    basic_stop "nfs"
	    ;;
	nlockmgr)
	    # Do nothing - used by statd-callout
	    :
	    ;;
	*)
	    usage
    esac
}

service_start ()
{
    case "$1" in
	nfs)
	    basic_start "nfs"
	    ;;
	nlockmgr)
	    # Do nothing - used by statd-callout
	    :
	    ;;
	*)
	    usage
    esac
}

##################################################
# Nitty gritty - monitoring and IP handling

# Check that a symlink exists, create it otherwise.
# Usage: check_ln <TARGET> <LINK>
check_ln ()
{
    if [ ! -L "${2}" ] ; then
        rm -vrf "${2}"
    else
        _t=$(readlink "${2}")
        if [ "$_t" != "${1}" ] ; then
            rm -v "${2}"
        fi
    fi
    # This is not an "else".  It also re-creates the link if it was
    # removed above!
    if [ ! -e "${2}" ]; then
        ln -sfv "${1}" "${2}"
    fi
}

# Return 'active' if the shared filesystem is accessible.
get_cluster_fs_state ()
{
    case $state_fs in
        gpfs)
            /usr/lpp/mmfs/bin/mmgetstate | awk 'NR == 4 { print $3 }'
            ;;
        glusterfs)
            # Since we're past create_ganesha_recdirs(), we're active.
            echo "active"
            ;;
        *)
            echo "File system $state_fs not supported"
            exit 1
            ;;
   esac
}

create_ganesha_recdirs ()
{
    if ! _mounts=$(mount | grep $state_fs); then
      echo "Failed to find mounts of type $state_fs"
      exit 1
    fi
    if [ -z "$_mounts" ]; then
      echo "startup $state_fs not ready"
      exit 0
    fi

    case $state_fs in
        gpfs)
            _mntpt=$(echo "$_mounts" | sort | awk 'NR == 1 {print $3}')
            _link_dst="${_mntpt}/${ganesha_rec_subdir}"
            mkdir -vp "$_link_dst"
            check_ln "$_link_dst" "$GANRECDIR"
            ;;
        glusterfs)
            [ -d /var/lib/nfs.backup ] || mv /var/lib/nfs /var/lib/nfs.backup
            check_ln ${NODESTATEDIR} /var/lib/nfs

            mkdir -p ${NODESTATEDIR}/ganesha/v4recov
            mkdir -p ${NODESTATEDIR}/ganesha/v4old
            mkdir -p ${NODESTATEDIR}/statd/sm
            mkdir -p ${NODESTATEDIR}/statd/sm.bak
            touch ${NODESTATEDIR}/state
            touch ${NODESTATEDIR}/statd/state

            mkdir -p ${GANSTATEDIR}
            check_ln ${NODESTATEDIR} ${NODESTATELN}
            for node in `ls ${GANSTATEDIR}`; do
                if [ "${node}" != "${host}" ]; then
                    check_ln ${GANSTATEDIR}/${node}/ganesha ${NODESTATEDIR}/ganesha/${node}
                    check_ln ${GANSTATEDIR}/${node}/statd ${NODESTATEDIR}/statd/${node}
                fi
            done
            ;;
   esac
}

service_check ()
{
    create_ganesha_recdirs

    # Always succeed if cluster filesystem is not active
    _cluster_fs_state=$(get_cluster_fs_state)
    if [ $_cluster_fs_state != "active" ] ; then
	return 0
    fi

    # Check that NFS Ganesha is running, according to PID file
    _pidfile="/var/run/ganesha.pid"
    _ganesha="/usr/bin/ganesha.nfsd"
    if ! { read _pid < "$_pidfile" && \
	   grep "$_ganesha" "${procfs}/${_pid}/cmdline" ; } >/dev/null 2>&1 ; then
	echo "ERROR: NFS Ganesha not running according to PID file"
	return 1
    fi

    return 0
}

#-------------------------------------------------

nfs_releaseip ()
{
    if [ -x "/usr/bin/grace_period" ]; then
        /usr/bin/grace_period "2:${2}"
    else
        dbus-send --print-reply --system --dest=org.ganesha.nfsd \
        /org/ganesha/nfsd/admin org.ganesha.nfsd.admin.grace \
        string:"2:${2}"
    fi
}

nfs_takeip ()
{
    case  $state_fs in
        glusterfs)
            check_ln ${NODESTATEDIR} ${GANSTATEDIR}/${2}
            ;;
    esac
    if [ -x "/usr/bin/grace_period" ]; then
        /usr/bin/grace_period "5:${2}"
    else
        dbus-send --print-reply --system --dest=org.ganesha.nfsd \
        /org/ganesha/nfsd/admin org.ganesha.nfsd.admin.grace \
        string:"5:${2}"
    fi
}

##################################################
# service init startup and final shutdown

nfs_shutdown ()
{
    basic_stop "nfs"
}

nfs_startup ()
{
    basic_stop "nfs" || true

    create_ganesha_recdirs

    basic_start "nfs"
    _f="${procfs}/sys/net/ipv4/tcp_tw_recycle"
    if [ "$_f" ] ; then
	echo 1 >"$_f"
    fi
}

##################################################
# list share directories

nfs_monitor_list_shares ()
{
    grep Path $nfs_exports_file |
	cut -f2 -d\" |
	sort -u
}

##################################################

nfs_register ()
{
    cat <<EOF
shutdown
startup
stop
start
check
releaseip
takeip
monitor-list-shares
EOF
}

##################################################

action="$1"
shift

case "$action" in
    shutdown)            nfs_shutdown            ;;
    startup)             nfs_startup             ;;
    stop)                service_stop "$1"       ;;
    start)               service_start "$1"      ;;
    check)               service_check "$1"      ;;
    releaseip)           nfs_releaseip "$@"      ;;
    takeip)              nfs_takeip "$@"         ;;
    monitor-list-shares) nfs_monitor_list_shares ;;
    register)            nfs_register            ;;
    monitor-pre|monitor-post|releaseip-pre|takeip-pre)
	# Not required/implemented
	:
	;;
    *)
	usage
esac
