#!/bin/ksh
#
#pragma	ident   "@(#)nfs_mount.sh 1.4   01/03/28 SMI"
#Copyright (C)	1997 Sun Microsystems, Inc.
#

# Usage: nfs_mount SERVERNAME FSNAME MP ISREMOTE

# This program does an 'end-to-end' test of being able to use
# servername to access the filesystemname.  It mounts the file
# system.
# Exits: 0 for complete success,
#        99 for failed and takeover should be considered,
#        98 for failed benignly due to access/permission denied: this
#           exit code should be treated as indicating that the probee
#           is healthy, but cannot do complete probes for this file system.
#        97 for failed such that probefile does not exist but the root
#           dir of the mounted file system does, so MP can be used to
#           probe the file system.
#        other non-zero otherwise.
# Note: only cases 0 or 97 pay attention to UMOUNTEND==0: the other cases
# all do a umount at the end regardless.

# HA-NFS currently doesn't support either of the NFS security options
# "secure" and "kerberos".
# If we ever do support them, this probing code will have to be modified to
# look at the share table entry for a file system and issue the
# appropriate secure/kerberos mount command.
# If the server should happen to be exporting a file system as
# "secure" or "kerberos", the mount attempt by the probe code below
# (or file r/w) will fail but with an error that our probes are smart
# enough to know means that we shouldn't takeover (downside: we don't
# get to probe that file system either!).
#
# We don't need to look at the read-only share
# option.  We can just do ordinary read/write mount and downstream
# the attempts to write a file will fail with EROFS.  This is fine,
# because our detection code knows to ignore EROFS.
#
# XXX Now using soft mount.  But what is its timeout?  Consider
# XXX whether we must tune retrans=n option.
# Answer: On Solaris 5.4, with defaulting the timeo= and retrans=
# nfs mount options (i.e., not supplying those options to the mount),
# the nfs_touchfile program took 43 seconds elapsed time before
# it exited with the error errno==145 ETIMEDOUT.

# We look at exit codes and discriminate by which ones are
# really remote errors that indicate a problem with the
# remote machine (not our machine, like ENOBUFS).  Our current
# theory is that only errno==ETIMEDOUT is definitely a problem
# with the remote machine -- all the other communication error
# codes indicate a problems with ourselves or the network or our
# ability to "reach" the network.
#
#
# In Energizer Cluster Framework, this is called from nfs_mon.
#
pre="SUNWcluster.nfs_mount"

cleanup()
{
    /bin/rm -f ${TRYMTMP} ${TMPERR}
    if [ ${UMOUNTEND} -eq 1 ]; then
        nfs_just_umount ${MP} >/dev/null 2>&1
    fi
}

cleanuptrap()
{
    UMOUNTEND=1
    cleanup
}

# Usage: doexit exitstatus
doexit()
{
    typeset doexitrc

    doexitrc=$1
    if [ ${doexitrc} -eq 0 -o ${doexitrc} -eq ${FAILEDUSEMP} ]; then
        cleanup
        exit ${doexitrc}
    else
        UMOUNTEND=1
        cleanup
        exit ${doexitrc}
    fi
}

# Usage: nfstrymount serverName:serverPath clientMountPoint
#
# proto=udp
#
# We are using soft mounts.  Therefore, we use proto=udp, not tcp,
# because the udp rpc path will take care of doing a small number of
# retries before we give up, whereas the soft mount tcp path has no
# retries at the rpc level (and the retries tcp itself does do not
# handle various oddities like a server being killed and restarted
# causing the tcp connection to be in a funky state, which needs
# at least one rpc level retry to mask it).  (The bottom line is
# that soft mounts aren't taken very seriously.)
# N.B. If we omitted the proto= option, it would default to tcp,
# so that is why we have to explicitly request proto=udp.
#
# When we tried using soft mounts over tcp, we saw the following
# problems with the mount_nfs command:
# 1. Sometimes the nfs client code in the kernel would
# print the following message on syslog, and then return EIO to
# the mount command:
#  <date> <hostname> unix: NFS fsinfo failed for server <logicalhost>: 
# error 27 (RPC: Received disconnect from remote)
# The nfs mount command would then print "I/O error" and exit non-zero.
# 2. Like (1), but the message printed to syslog was about a timeout:
#  <date> <hostname> unix: NFS fsinfo failed for server <logicalhost>: 
# error 5 (RPC: Timed out)
# This error bubbled out to the nfs mount command as ETIMEDOUT causing
# the command to print "Connection timed out" and exit non-zero.
# This scenario is worse than (1) because the rest of our fault
# monitoring logic treats "Connection timed out" as one of the few
# cases which trigger a takeover.
# 3. In addition, there were lots of occurences of syslog'ing:
#   "NFS server <logicalhost> not reponding still trying"
# followed by
#   "NFS server <logicalhost> ok"
# in rapid succession (little pause between those two messages)
# multiple times on the probing host.  At the very least, these messages
# would be likely to confuse system administrators.
nfstrymount()
{
    typeset NFSTMP_SRVNAMEPATH NFSTM_MP NFSTMP_TIMEOUTRC NFSTM_HAVEPROTOUDP
    typeset NFSTM_FLAGS NFSTM_CMD NFSTM_DENIED_ATTEMPTS NFSTM_CNT RC

    # Tries to do the mount under a timeout.
    # Return value says what to do (okay, takeover, failednotakeover)
 
    # Grab the two arguments:
    NFSTM_SRVNAMEPATH="$1"
    NFSTM_MP="$2"
 
    NFSTM_TIMEOUTRC=88
    NFSTM_HAVEPROTOUDP=1
    NFSTM_FLAGS="-F nfs -m -o fg,retry=5,soft,noac,proto=udp,retrans=10"
    NFSTM_CMD="mount ${NFSTM_FLAGS} ${NFSTM_SRVNAMEPATH} ${NFSTM_MP}"

    # Sometimes the mount gets intermittent spurious 'access denied' errors.
    # We do not know why.  We deal with it by retrying a small number
    # of times.
    NFSTM_DENIED_ATTEMPTS=3
    NFSTM_CNT=0
    while : ; do
        child_timeout -e ${NFSTM_TIMEOUTRC}  \
		-t ${HA_FM_NFS_TIMEOUT_MOUNTD} ${NFSTM_CMD} > ${TRYMTMP} 2>&1
        RC=$?

        if [ ${RC} -eq 0 ]; then
            /bin/rm -f ${TRYMTMP}
            return 0
        fi

        # See if the 'proto=udp' gave an invalid option.  That will
        # happen if this machine is older than Solaris 2.5.  If it
        # did, try getting rid of the ',proto=udp' substring and
        # try again.
        if /bin/grep -i 'invalid option' ${TRYMTMP} >/dev/null && \
           [ ${NFSTM_HAVEPROTOUDP} -eq 1 ]
        then
            NFSTM_HAVEPROTOUDP=0
            NFSTM_FLAGS="`echo ${NFSTM_FLAGS} | sed -e 's/,proto=udp//'`"
            NFSTM_CMD="mount ${NFSTM_FLAGS} ${NFSTM_SRVNAMEPATH} ${NFSTM_MP}"
            continue
        fi

        if /bin/grep -i 'denied' ${TRYMTMP} >/dev/null ; then
            NFSTM_CNT=`expr ${NFSTM_CNT} + 1`
            if [ ${NFSTM_CNT} -ge ${NFSTM_DENIED_ATTEMPTS} ]; then
	                break
            fi
            sleep 60
        else
            break
        fi
    done

    if [ ${RC} -eq ${NFSTM_TIMEOUTRC} ]; then
        log_info "${pre}.1000" \
		 "mount failed (timed out): ${NFSTM_CMD}"
    else
        log_info "${pre}.1001" \
		"mount failed: ${NFSTM_CMD}"
    fi

    if [ -s ${TRYMTMP} ]; then
        echo "Error messages from failed mount command are:"
        /bin/cat ${TRYMTMP}
    fi

    # The egrep for 'denied' is intended to match both 'access denied'
    # and 'Permission denied'.

    if /bin/grep -i 'denied' ${TRYMTMP} >/dev/null ; then
        log_info "${pre}.5100" "The mount command failed"
        return ${FAILEDBENIGN}
    fi

    if [ ${RC} -eq ${NFSTM_TIMEOUTRC} ] || \
       /bin/egrep -i 'server not responding|service not responding|Connection timed out' ${TRYMTMP} >/dev/null
    then 
        # 'Stale' is for "Stale NFS file handle".  Match on just stale
        # in case precise wording changes.
        /bin/egrep -i 'access denied|Permission denied|Stale' ${TRYMTMP} >/dev/null
        if [ $? -ne 0 ]; then
            if [ ${ISREMOTE} -eq 1 ]; then
		log_info "${pre}.4201" \
                "mount failed with non-response problem, so will consider takeover"
            else
		log_info "${pre}.4202" \
                "mount from myself failed with non-response problem"
            fi
            /bin/rm -f ${TRYMTMP}
            return ${FAILEDTAKEOVER}
        fi
    fi

    if [ ${ISREMOTE} -eq 1 ]; then
        log_info "${pre}.5001" \
	 "mount failed for legitimate or unknown reason, so will not takeover"
    else
	log_info "${pre}.5002" \ 
		"mount from myself failed for legitimate or unknown reason"
    fi
   /bin/rm -f ${TRYMTMP}
    return ${FAILEDNOTAKEOVER}
}


# Main

PROG=`/bin/basename $0`
NEEDEDARGS=3
if [ $# -ne ${NEEDEDARGS} ]; then
    echo "Wrong number of arguments: $# but should be ${NEEDEDARGS}"
    exit 1
fi

SERVERNAME=$1
FSNAME=$2
MP=$3
UMOUNTEND=0
ISREMOTE=1
FMBIN=/opt/SUNWcluster/ha/nfs
PATH=${PATH}:${FMBIN}
. ${FMBIN}/nfs_common_util

TMP=/var/opt/SUNWcluster/run
TRYMTMP=${TMP}/${PROG}.trymount.$$
TMPERR=${TMP}/${PROG}.err.$$

FAILEDTAKEOVER=99
FAILEDBENIGN=98
FAILEDUSEMP=97
FAILEDNOTAKEOVER=1
ETIMEDOUT=145
ENOENT=2
EACCES=13
EROFS=30

TRAPSIGNALS="1 2 3 15"
trap "cleanuptrap; trap 0; exit 1" $TRAPSIGNALS

# Create the $MP directory if needed.  We use -p switch to
# mkdir because the string $MP may contain more than one
# level of directory, e.g., ".../green/0"
if [ ! -d ${MP} ]; then
    mkdir -p ${MP} > /dev/null 2>&1
    if [ ! -d ${MP} ]; then
        echo "mkdir -p of mountpoint ${MP} failed"
        doexit 1
    fi
fi
nfstrymount ${SERVERNAME}:${FSNAME} ${MP}
RC=$?
if [ ${RC} -ne 0 ]; then
    if [ ${RC} -eq ${FAILEDTAKEOVER} ]; then
        # Suppress takeover if right flags are set in CCD.
        # The mount command needs both mountd and nfsd.
        # The fact that mount needs rpcbind is handled by try_command
        # Note that even though the mount command tries to use
        # the server's lockd in order to determine whether the
        # server supports locking, the mount command will proceed,
        # and use local locking, if the server's lockd does not
        # respond.  Therefore, we do not have to consider whether
        # or not we are suppressing takeover for locking.
        if [ ${HA_FM_NFS_SUPPRESSTAKEOVER_MOUNTD} -eq 1 -o \
             ${HA_FM_NFS_SUPPRESSTAKEOVER_NFSD} -eq 1 ]
        then
            if [ ${ISREMOTE} -eq 1 ]; then
                log_info "${pre}.4001" \
"mountd/nfsd for ${SERVERNAME}:${FSNAME} not responding but configured to suppress takeover for this problem"
            else
                log_info "${pre}.4002" \
			"my mountd/nfsd for ${SERVERNAME}:${FSNAME} not responding"
            fi
            RC=1
        fi
    fi
    doexit ${RC}
fi
exit 0
