#!/bin/bash
# -*- mode: sh; c-basic-offset: 4; tab-width: 8; indent-tabs-mode: nil -*-
# vi: set shiftwidth=4 tabstop=8 softtabstop=4 expandtab:
# :indentSize=4:tabSize=8:noTabs=true:
# vim: filetype=sh

# ---- error handling
set -o errexit;
set -o posix;
set -o pipefail;
set -o errtrace;
set -o nounset;

unexpected_error() {
    local errstat=$?
    echo "${g_prog:-$(basename "$0")}: Error! Encountered unexpected error at 'line $(caller)', bailing out..." 1>&2
    exit $errstat;
}
trap unexpected_error ERR;

# ---- error functions

merror() {
    echo "$g_prog: Error! ""$@" 1>&2;
    exit 1;
}
minterror() {
    echo "$g_prog: Internal Error! ""$@" 1>&2;
    exit 1;
}
mwarn() {
    echo "$g_prog: Warning! ""$@" 1>&2;
}

# ---- usage

usage() {
    local exitstat=2;
    if [[ ! -z "${2:-}" ]] ; then
        if [[ ! $2 =~ [[:digit:]]+ ]] ; then
            minterror "usage(): exitstat ($2) must be numeric."
        fi
        exitstat=$2;
    fi

    # Only redirect to stderr on non-zero exit status
    if [[ $exitstat -ne 0 ]] ; then
        exec 1>&2;
    fi

    if [[ ! -z "${1:-}" ]] ; then
        echo "$g_prog: Error! $1"
    fi

    echo "Usage: $g_prog [-h|--help] \\"
    echo "               [-v|--verbose]"
    echo ""
    echo "         -v|--verbose  -- verbose mode, print commands that are executed"
    echo "         -h|--help     -- print this usage";
    echo "";

    if [[ $exitstat -ne 0 ]] ; then
        # Print error again, useful for long usages messages
        if [[ ! -z "${1:-}" ]] ; then
            echo ""
            echo "$g_prog: Error! $1"
        fi
    fi

    # bash only:
    if [[ $exitstat -ne 0 ]] ; then
        echo "  at: $(caller)";
    fi
    exit $exitstat;
}

# ---- argument parsing

achk() {
    if [[ $1 -eq 0 ]] ; then usage "Missing argument to $2 option"; fi
}

parseargs() {
    opt_verbose=false;

    while [[ $# != 0 ]]; do
        opt="$1"; shift;
        case "$opt" in
            # Flag with no argument example:
            #   --flag|--fla|--fl|--f)
            #     opt_flag=true;;
            # Option with argument example:
            #   --arg|--ar|--a)
            #     achk $# $opt; opt_somearg=$1; shift;;
            -v|--verbose) opt_verbose=true;;
            -h|-help|--help|--hel|--he|--h) usage "" 0;;
            -*) usage "Unrecognized option: $opt";;
            *)
                local argstr=${1+"$@"};
                usage "Unexpected extraneous arguments detected: $opt $argstr"
                ;;
        esac
    done
}

# ---- globals

set_preglobals() {
    # Force the path to only what we need (/sbin needed for ifconfig)
    PATH_ORIG=$PATH
    PATH="/usr/bin:/bin"

    g_prog=$(basename "$0");
    g_progdir=$(dirname "$0");
    g_progdir_abs=$(dirname "$(readlink -f "$0")");

    # ---- global env

    . "$g_progdir_abs/../../private/runtime-common/lib/globalenv.ish"
}

set_globals() {
    # verbose
    g_verbose_level=0;
    if $opt_verbose; then
        g_verbose_level=1;
    fi
}


# ---- subroutines

checkmount_timeout() {
    local timeout=$1; shift;
    local mount=$1; shift;

    # Detect a timeout if the stat command does not exit in the specified
    # amount of time.

    # Use the 'timeout' command in coreutils, rather than the 'read -t'
    # bash builtin (which requires us to turn off posix mode, and monkey
    # around with command redirection and supplying in put for the case
    # that stat doesn't produce any stdout).  This should also avoid
    # the 'checkmounts: line 148: echo: write error: Broken pipe' we
    # sometimes see in installer from this line:
    #
    #     read -t${timeout} < <(stat -t "$mount" 2> /dev/null || true; echo "dummy") || stat=$?
    #
    # We will ignore any stdout, stderr or non-timeout related exit status
    # for the stat command:
    local stat=0;
    timeout "$timeout" $g_progdir/stat -t "$mount" > /dev/null 2>&1 || stat=$?

    # We only want to report timeout errors, not any other type of error that
    # 'stat' or 'timeout' can produce.  For the 'read -t' case we would check
    # for exit status greater than 128 (according to the bash(1) man page).
    # For the 'timeout' command we will expect 124 in the case of a timeout,
    # or 137 (128+9) for the case when specifing '-s KILL' to the timeout
    # command.  Anything else should not be treated as a timeout, so we will
    # return zero status for those.
    if [[ $stat -eq 124 ]] || [[ $stat -eq 137 ]] ; then
        return $stat;
    fi
    return 0;
}

# ---- main

# Save off original cmdline args, use as "${g_origargs[@]}" (with doublequotes)
g_origargs=( ${1+"$@"} )

set_preglobals ${1+"$@"};
parseargs ${1+"$@"};
set_globals;

echo "  Checking mount points..."

# Read all mountpoints from the second field in /proc/mounts.  That approach is
# more reliable than depending on the 'mount(1)' output, which does not escape
# any space characters in the mount source or destination.  Also it might be
# more reliable than using the 'findmnt' command (via something like 'LANG=C
# findmnt -v --list -o target' or 'LANG=C findmnt -v --pairs -o target'), as
# it may be possible for 'findmnt' to hang on hung mountpoints).
#
# Note that /proc/mounts escapes special characters, so we will be guaranteed
# that field 2 is always the entire mountpoint, but we will need to process
# the special characters below before using the mountpoint path.  The
# getmntent(3) man page, specifies the format of the escapes.  Basically, '\\'
# or '\134' for backslash, `\040' for space, `\011' for tab, '\012' for
# newline.  In general, '\ooo' backslash followed by three octal characters
# whould map to their ascii equivalent.
#
# This could be used to read the mountpoints, with escaped characters resolved
# from /proc/mounts directly into a bash array (but for this case, it may be
# to process the escape characters when using the mountpont path below);
#
#     eval mountpts=( $(perl -nae '$_="$F[1]"; s,\\\\,\\134,g; s/\\([0-7]{3})/sprintf("%c",oct($1))/eg; print "\"$_\"\n";' /proc/mounts) );
#
mountpts=( $(cat /proc/mounts | awk '{print $2}') );

slowmountpts=()
for mountpt_esc1 in "${mountpts[@]}" ; do
    # Resolve the escaped characters that may be in the mountpoint from
    # /proc/mounts (as described in the getmntent(3) man page), basically `\\`
    # and '\ooo' backslash + three octal numbers.
    #
    # We have two strategies for this.  The first using perl:
    #
    #    mountpt=$(echo "$mountpt_esc1" | perl -pe 's,\\\\,\\134,g; s/\\([0-7]{3})/sprintf("%c",oct($1))/eg;');
    #
    # and the second using the 'printf "%b"' bash builtin to resolve escaped
    # characters.  Note that the "%b" will prefer four octal digits over
    # three when converting.  So 'foo\04066bar' will not resolve to 'foo 66bar'
    # as expected.  So we make sure to prpend a leading 0 after the escape to
    # handle that case, so the "%b" will see 'foo\004066bar' instead, which
    # will produce the expected results:
    #
    #    mountpt_esc2=$(echo "$mountpt_esc1" | sed -e 's,\\\\,\\134,g' -e 's/\\\([0-7]\{3\}\)/\\0\1/g')
    #    mountpt=$(printf "%b\n" "$mountpt_esc2")
    #
    # We will use the bash 'printf "%b"' method to avoid extra dependency on
    # perl.
    mountpt_esc2=$(echo "$mountpt_esc1" | sed -e 's,\\\\,\\134,g' -e 's/\\\([0-7]\{3\}\)/\\0\1/g')
    mountpt=$(printf "%b\n" "$mountpt_esc2")
    if ! checkmount_timeout 1 "$mountpt" ; then
        if [[ $g_verbose_level -ge 1 ]] ; then
            echo "      Detected slow mount point: $mountpt"
        fi
        slowmountpts+=( "$mountpt" );
    fi
done

if [[ ! -z "${slowmountpts[@]+${slowmountpts[@]}}" ]] ; then
    echo "    Rechecking slow mount points..."
    hungmountpts=();
    for mountpt in "${slowmountpts[@]}" ; do
        if [[ $g_verbose_level -ge 1 ]] ; then
            echo "      Rechecking slow mount point: $mountpt"
        fi
            checkmount_timeout 10 "$mountpt" || hungmountpts+=( "$mountpt" );
    done

    if [[ ! -z "${hungmountpts[@]+${hungmountpts[@]}}" ]] ; then
        echo
        echo "    Detected unresponsive mount point(s):"
        for mountpt in "${hungmountpts[@]}"; do
            echo "        $mountpt"
        done
        echo
        echo "    Unresponsive mounts will cause the WSO2 component of SMRT Link"
        echo "    services to hang on startup.  The unresponsive mount points"
        echo "    must be fixed before starting SMRT Link services."
        echo ""
        exit 1;
    fi
fi

if [[ $g_verbose_level -ge 1 ]] ; then
    echo "  No hung mount points detected."
fi
exit 0;
