#!/bin/bash
# vim: set ts=4 ft=sh ff=unix et foldmethod=marker fdl=0 fdc=3:
#
# $Id: xfs_sb 1257 2012-01-19 08:39:14Z root $
# $Date: 2012-01-19 17:39:14 +0900 (Thu, 19 Jan 2012) $
# $Revision: 1257 $
# $Author: root $
# $HeadURL$
# svn propset svn:keywords "Id date rev author headurl" *

# version
VERSION_STRING="0.9"
REVISION="$Revision: 1257 $"
REVISION=${REVISION//: /}
REVISION=${REVISION// \$/}

# define
SUCCESS=0
FAILURE=1
CHECK_FAILURE=2

# require
AWK="/bin/awk"
CAT="/bin/cat"
FSTAB="/etc/fstab"
GREP="/bin/grep"
MOUNTS="/proc/mounts"
XFS_DB="/bin/xfs_db.mount"
XFS_INFO="/bin/xfs_info"
XFS_GROWFS="/bin/xfs_growfs"

XFS_SB_LOG="/var/log/xfs_sb.log"

# name   : check_command
# usage  :
# args   : NONE
# return : NONE
function check_command()
{
#{{{

    # check require command
    for cmd in $AWK $CAT $FSTAB $GREP $MOUNTS $XFS_DB $XFS_INFO $XFS_GROWFS
    do
        if [ ! -e $cmd ]; then
            print_message $cmd not found !!!
            exit $FAILURE
        fi
    done

#}}}
}

# name   : error
# usage  : error EXIT_STATUS EXIT_MESSAGE
# args   : $1 - EXIT_STATUS
#          $2 - EXIT_MESSAGE
# return : NONE
function error()
{
#{{{
    status="$1"
    shift;
    test -n "$0" && echo -n "$0: "
    echo "$@"
    test $SUCCESS -ne "$status" && exit "$status";
#}}}
} 1>&2

# name   : error
# usage  : error EXIT_STATUS EXIT_MESSAGE
# args   : $1 - EXIT_STATUS
#          $2 - EXIT_MESSAGE
# return : NONE
# Tests whether *entire string* is numerical.
# In other words, tests for integer variable.
function isdigit()
{
#{{{
    [ $# -eq 1 ] || return $FAILURE

    case $1 in
        *[!0-9]*|"")
            return $FAILURE
            ;;
        *) 
            return $SUCCESS
            ;;
    esac
#}}}
}

function check_null()
{
#{{{
    if [ x"$1" != "x" ]; then
        return $FAILURE
    else
        return $SUCCESS
    fi
#}}}
}

# name   : help_and_exit
# usage  :
# args   :
# return :
function help_and_exit()
{
#{{{
    $CAT <<EOF

$0 : xfs superblock consistency check tools

usage:
  -a            adjust superblock consistency
  -c            check superblock consistency
  -d            debug mode
  -h            display this help and exit
  -v            output version information and exit

ex:
$0 -c all       : check all mounted xfs volume
$0 -c /volume   : check /volume
$0 -a /volume   : adjust /volume

EOF
    exit $SUCCESS
#}}}
}

# name   :
# usage  :
# args   :
# return :
function version_and_exit()
{
#{{{
    $CAT <<EOF
$0 version $VERSION_STRING.$REVISION
EOF
    exit $SUCCESS
#}}}
}

# name   : logging
# usage  :
# args   :
# return :
function logging()
{
#{{{
    local time

    time=`date +"%m/%d/%y %H:%M:%S"`

    $CAT <<EOF >> $XFS_SB_LOG
$time >> $@
EOF
#}}}
}

# name   : print_message
# usage  :
# args   :
# return :
function print_message()
{
#{{{

    logging $@

    $CAT <<EOF
$@
EOF
#}}}
}

# name   : sb_check
# usage  :
# args   :
# return :
function sb_check()
{
#{{{

    VOLUME=$1

    VOLUME=${VOLUME%/}

    # get device at fstab
    DEVICE=`$GREP -v "^#" $FSTAB | $GREP -P "[\t ]+$VOLUME[\t ]+" | $AWK '{print $1}'`

    if check_null "$DEVICE" ; then
        logging $VOLUME not found at $FSTAB
        RET=$CHECK_FAILURE
        return $RET
    fi

    # check device, mount point at /proc/mounts
    $GREP -q "$DEVICE $VOLUME" $MOUNTS
    if [ $? -ne $SUCCESS ]; then
        logging $VOLUME found. but $VOLUME and $DEVICE not match
        RET=$CHECK_FAILURE
        return $RET
    fi

    # get xfs_info agcount, sb0 agcount
    xfs_info_agcount=`$XFS_INFO $VOLUME | $GREP agcount | $AWK -F, '{print $1}'  | $AWK -F= '{print $4}'`
    sb0_agcount=`$XFS_DB -x -c "sb 0" -c "p agcount" $DEVICE | $AWK '{print $3}'`

    if ! isdigit $xfs_info_agcount || ! isdigit $sb0_agcount ; then
        logging {xfs_info|sb0}_agcount $xfs_info_agcount, $sb0_agcount value is not digital 
        RET=$CHECK_FAILURE
        return $RET
    fi

    # get dblocks
    xfs_info_dblocks=`$XFS_INFO $VOLUME | $GREP "^data" | $AWK -F, '{print $1}' | $AWK -F= '{print $4}'`
    sb0_dblocks=`$XFS_DB -x -c "sb 0" -c "p dblocks" $DEVICE | $AWK '{print $3}'`

    if ! isdigit $xfs_info_dblocks || ! isdigit $sb0_dblocks ; then
        logging {xfs_info|sb0}_dblocks $xfs_info_dblocks, $sb0_dblocks value is not digital 
        RET=$CHECK_FAILURE
        return $RET
    fi

    # get xfs_info maxpct, sb0 maxpct
    xfs_info_maxpct=`$XFS_INFO $VOLUME | $GREP "^data" | $AWK -F, '{print $2}' | $AWK -F= '{print $2}'`
    sb0_maxpct=`$XFS_DB -x -c "sb 0" -c "p imax_pct" $DEVICE | $AWK '{print $3}'`

    if ! isdigit $xfs_info_maxpct || ! isdigit $sb0_maxpct ; then
        logging {xfs_info|sb0}_maxpct $xfs_info_maxpct, $sb0_maxpct value is not digital 
        RET=$CHECK_FAILURE
        return $RET
    fi

    if [ x"$xfs_info_agcount" == x"$sb0_agcount" -a x"$xfs_info_dblocks" == x"$sb0_dblocks" \
	-a x"$xfs_info_maxpct" == x"$sb0_maxpct" ]; then
        # match xfs_info, sb0
        RET=$SUCCESS
        return $RET
    else
        # not match xfs_info, sb0
        RET=$FAILURE
        return $RET
    fi


#}}}
}

# name   : sb_check_all
# usage  :
# args   :
# return :
function sb_check_all()
{
#{{{

    local check_ret check_all_ret

    XFS_MOUNT_LIST=`df -Pt xfs | $GREP -v "Filesystem " | $AWK '{print $6}'`

    echo ""
    print_message "found mounted xfs filesystem" $XFS_MOUNT_LIST
    echo ""

    check_all_ret=$SUCCESS
    for VOLUME in $XFS_MOUNT_LIST
    do
        sb_check "$VOLUME"
        check_ret=$?
        if [ $check_ret -eq $SUCCESS ]; then
            (( check_all_ret += $check_ret ))
            print_message $VOLUME is normal
        elif [ $check_ret -eq $CHECK_FAILURE ]; then
            (( check_all_ret += $check_ret ))
            print_message $VOLUME check problem. check log.
        else
            (( check_all_ret += $check_ret ))
            print_message $VOLUME is abnormal
        fi 
    done

    RET=$check_all_ret
    return $RET

#}}}
}

# name   : sb_adjust
# usage  :
# args   :
# return :
function sb_adjust()
{
#{{{

    VOLUME=$1
    VOLUME=${VOLUME%/}

    # get device at fstab
    DEVICE=`$GREP -v "^#" $FSTAB | $GREP -P "[\t ]+$VOLUME[\t ]+" | $AWK '{print $1}'`

    if check_null "$DEVICE" ; then
        logging $VOLUME not found at $FSTAB
        RET=$CHECK_FAILURE
        return $RET
    fi

    # check device, mount point at /proc/mounts
    $GREP -q "$DEVICE $VOLUME" $MOUNTS
    if [ $? -ne $SUCCESS ]; then
        logging $VOLUME found. but $VOLUME and $DEVICE not match
        RET=$CHECK_FAILURE
        return $RET
    fi

    if ! sb_check "$VOLUME" ; then
        # adjust agcount, dblocks, maxpct
        xfs_info_agcount=`$XFS_INFO $VOLUME | $GREP agcount | $AWK -F, '{print $1}' | $AWK -F= '{print $4}'`
        xfs_info_dblocks=`$XFS_INFO $VOLUME | $GREP "^data" | $AWK -F, '{print $1}' | $AWK -F= '{print $4}'`
	xfs_info_maxpct=`$XFS_INFO $VOLUME | $GREP "^data" | $AWK -F, '{print $2}' | $AWK -F= '{print $2}'`

        if ! isdigit $xfs_info_agcount || ! isdigit $xfs_info_dblocks || \
	    ! isdigit $xfs_info_maxpct ; then
            logging $xfs_info_agcount, $xfs_info_dblocks, $xfs_info_maxpct value is not digital 
            RET=$CHECK_FAILURE
            return $RET
        fi

        $XFS_DB -x -c "sb 0" -c "write agcount $xfs_info_agcount" $DEVICE >&/dev/null
        $XFS_DB -x -c "sb 0" -c "write dblocks $xfs_info_dblocks" $DEVICE >&/dev/null
        $XFS_DB -x -c "sb 0" -c "write imax_pct $xfs_info_maxpct" $DEVICE >&/dev/null
        $XFS_GROWFS $VOLUME >&/dev/null
        sleep 1

        if ! sb_check "$VOLUME" ; then
            logging try $VOLUME adjust. but $VOLUME adjust fail.
            RET=$FAILURE
            return $RET
        fi
    fi

    RET=$SUCCESS
    return $RET

#}}}
}

###########################################################
# main
###########################################################

RET=$FAILURE

if [ "$#" -le 1 ]; then
    help_and_exit
fi

while getopts "acdhv" OPT
do
    case $OPT in
        'a')
            ADJUST=1
            ;;
        'c')
            CHECK=1
            ;;
        'd')
            DEBUG=1
            ;;
        'h')
            help_and_exit
            ;;
        'v')
            version_and_exit
            ;;
        '?')
            error 1 "getopts error"
            ;;
        *)
            error 1 "getopts error"
            ;;
    esac
done

shift $(($OPTIND - 1))

# check command
check_command

if [ x"$CHECK" == x"1" ]; then

    if [ x"$1" != x"all" ]; then

        echo ""
        if sb_check "$1"; then
            print_message $1is normal
        elif [ $? -eq $CHECK_FAILURE ]; then
            print_message $1 check problem. check log.
        else
            print_message $1 is abnormal
        fi

    else
        sb_check_all
    fi
    echo ""

elif [ x"$ADJUST" == x"1" ]; then

    echo ""
    if sb_adjust "$1"; then
        print_message $1 adjust is success
    elif [ $? -eq $CHECK_FAILURE ]; then
        print_message $1 adjust problem. check log.
    else
        print_message $1 adjust is fail
    fi 
    echo ""

else 
    echo help_and_exit
fi

exit $RET
