#! /bin/ksh -p
#
#ident "@(#)sccheck.ksh 1.17     04/09/02 SMI"
#
#  Copyright 2003-2004 Sun Microsystems, Inc.  All rights reserved.
#  Use is subject to license terms.
#

#
#
#	Startup wrapper for sccheck(1M)
#


typeset -r PROG=${0##*/} # equiv to `basename $0`
typeset -x TEXTDOMAIN=SUNW_SC_CMD
typeset -x TEXTDOMAINDIR=/usr/cluster/lib/locale
umask 0

#
# load common definitions and functions
#	exit immediately if load fails
#

integer status=0

typeset -r SC_CHECK_COMMON=/usr/cluster/lib/sccheck/sccheck_common
if [[ -e ${SC_CHECK_COMMON} ]]; then
    . ${SC_CHECK_COMMON} 2> /dev/null
    status=$?
else
    status=1
fi

if [[ $status -ne 0 ]]; then
    printf "$(gettext '%s: unable to load %s')\n" ${PROG} "${SC_CHECK_COMMON}" >&2
    exit 200
fi




#
# definitions used by client only
#

#
# command line flag -W (no warnings) maps
# to -s3 (filter out failures less than
# severity 3
typeset -i WARNING_SEVERITY=3

# java class for client-side
typeset -r JAVA_CLIENT=com.sun.cluster.sccheck.SccheckClient

# process ID of the JVM once it's invoked from this ksh
typeset JVM_PID=-1



#
# Test for number of active clients.
#
# Running clients create a file containing the process ID of
# the running sccheck.ksh. The number of pidfiles containing
# pids of active processes indicates the number of clients
# running on this node.
#
# Make sure all client pidfiles are legit before counting them.
#

typeset -r SCCHECK_PIDFILES=${SCCHECK_PIDFILES_BASE}/_sccheck.pid
common_clean_pidfiles ${SCCHECK_PIDFILES}

# this will be my pidfile if I survive validations and get to start up
typeset -r SCCHECK_PIDFILE=${SCCHECK_PIDFILES}.$$

#
# There may be more than one client executing concurrently
# from this node. Each client gets its own log files, 
# directory for unpacking explorer results, and tmp dir.
#
# Calculate $CLIENT_NUM as the number of client pidfiles
# present. Numbering starts at zero.
#
# Each client will age only its own log files and will cleanup its
# own unpack directory on exit. Only client 0 removes gzipped
# explorer results at startup.
typeset -i CLIENT_NUM=$(/usr/bin/ls -1 ${SCCHECK_PIDFILES}* 2> /dev/null | /usr/bin/wc -l)
typeset -r CLNUM_JDEFINE="-Dsccheck.clientnumber=${CLIENT_NUM}"

typeset -r SCCHECK_VARTMPDIR="/var/cluster/sccheck/tmp/client".${CLIENT_NUM}

#
# These variables will be filled in by user values and/or defaults
#
typeset HOSTLIST=	# the list of nodes to check
typeset OUTPUTDIR=	# dir to hold reports
typeset PUBLIC_NAMES=	# all cluster members
typeset CLUSTERMODE=	# true | false


#
# Explorer-related definitions:
#

# Directories for handling explorer results returned by servers.
typeset -r SCCHECK_EXP_BASE="/var/cluster/sccheck/explorers"

# Stores tar.gz explorer results returned by servers.
# Cleaned at sccheck start by client #0.
typeset -r SCCHECK_EXPS_GZDIR=${SCCHECK_EXP_BASE}"-gz"

# Client-side work directory for unpacking the explorer results.
# Each running client gets their own directory.
# Cleaned at end by java.
typeset -r SCCHECK_EXPS_UPKDIR=${SCCHECK_EXP_BASE}.${CLIENT_NUM}

typeset -x SINGLE_NODE_CHECK_FILE_PATH=${SCCHECK_LIB}/checklist.cluster.singlenode.xml
typeset -x MULTI_NODE_CHECK_FILE_PATH=${SCCHECK_LIB}/checklist.cluster.multinode.xml
typeset -x NON_CLUSTER_CHECK_FILE_PATH=${SCCHECK_LIB}/checklist.noncluster.xml
#
# Lists of "defines" handed to java at atartup.
#
# Parameters are passed in to JVM via -D defines rather
# than on the command line.
#
# Values appended as appropriate during execution of this script.
#
typeset CLUSTER_JDEFINES=	# cluster-related data
typeset ARG_JDEFINES=		# user/environment-related data


ARG_JDEFINES="${ARG_JDEFINES} -Dsccheck.explorersgzDir=${SCCHECK_EXPS_GZDIR}"
ARG_JDEFINES="${ARG_JDEFINES} -Dsccheck.explorersUnpackDir=${SCCHECK_EXPS_UPKDIR}"
ARG_JDEFINES="${ARG_JDEFINES} -Dsccheck.gunzip=${GUNZIP}"
ARG_JDEFINES="${ARG_JDEFINES} -Dsccheck.tar=${TAR}"

# client-side ke log file name
KE_JDEFINES="${KE_JDEFINES} -Dke.client.log=${SCCHECK_LOGDIR}/${KECLIENTLOG}.${CLIENT_NUM}"
typeset -r CLIENT_XSLDIR=${CLIENT_XSLBASEDIR}.${CLIENT_NUM}
KE_JDEFINES="${KE_JDEFINES} -Dke.client.xsldir=${CLIENT_XSLDIR}"

CLASSPATH=${CLASSPATH}:${CLIENT_XSLDIR}



#
#	Function definitions
#

#####################################################
#
# print_usage()
#
#	input:		nothing
#	output:		nothing
#	returns:	nothing
#
#	action: print usage message to stderr
#
#####################################################

print_usage()
{
    echo "$(gettext 'usage'): ${PROG} [-b] [-v <verbosity>] [-W | -s <severity>])"   >&2
    echo "\t\t$(gettext '[-h <hostlist>] [-o <report_directory>]')"                  >&2
    echo "\t\t$(gettext '[-c <single node mode check rule file>]')"                  >&2
    echo "\t\t$(gettext '[-C <multi node mode check rule file>]')"                   >&2
    echo "\t\t$(gettext '[-e <explorer input dirs | zip archives: dir1,dir2...>'])"  >&2
    return 0
}

#####################################################
#
# read_args()
#
#	input:		command-line args from user
#	output:		nothing
#	returns:	nothing
#
#	action: run getopts loop w/ validations
#		assign ARG_JDEFINES as appropriate
#
#	May exit the shell via exit_err()
#
#####################################################

read_args()
{

	integer vflg=${SC_FALSE}		# -v
	integer vvflg=${SC_FALSE}		# -vv
	integer Wflg=${SC_FALSE}		# -W
	integer sflg=${SC_FALSE}		# -s
	integer hflg=${SC_FALSE}	        # -h
	integer Cflg=${SC_FALSE}	        # -C

	#
	# Get command line options
	#

	OPTIND=1

	while getopts C:c:e:bv:Ws:h:o: opt 2>/dev/null
	do
		case ${opt} in

		b)	# brief
			ARG_JDEFINES="${ARG_JDEFINES} -Dsccheck.brief=true"
			;;

		v)	# verbosity level
			case "${OPTARG}" in
			0)	# same as default of no verbosity
			    ;;
			1)	# moderate verbosity
			    ARG_JDEFINES="${ARG_JDEFINES} -Dsccheck.verbose=true"
			    ;;
			2)	# max verbosity
			    ARG_JDEFINES="${ARG_JDEFINES} -Dsccheck.vverbose=true"
			    ;;
			*)	# bad option-argument
			    printf "$(gettext '%s: verbosity \"%s\" is not in the range 0-2')\n" "${PROG}" "${OPTARG}" >&2
			    print_usage
			    exit_err ${ERR_USAGE}
			    ;;
			esac
			;;  # v)

		W)	# suppress warnings; equivalent '-s ${WARNING_SEVERITY}'
			if [[ ${sflg} = ${SC_TRUE} ]]; then
			    printf "$(gettext '%s: -W and -s may not be used together.')\n" "${PROG}" >&2
			    print_usage
			    exit_err ${ERR_USAGE}
			fi
			Wflg=${SC_TRUE}
			ARG_JDEFINES="${ARG_JDEFINES} -Dsccheck.severity=${WARNING_SEVERITY}"
			;;

		s)	# severity filter
			if [[ ${Wflg} = ${SC_TRUE} ]]; then
			    printf "$(gettext '%s: -W and -s may not be used together.')\n" "${PROG}" >&2
			    print_usage
			    exit_err ${ERR_USAGE}
			fi

			sflg=${SC_TRUE}
			case "${OPTARG}" in
			1|2|3|4)
			    ;;
			*)
			    printf "$(gettext '%s: severity \"%s\" is not in the range 1-4')\n" "${PROG}" "${OPTARG}" >&2
			    print_usage
			    exit_err ${ERR_USAGE}
			    ;;
			esac
			ARG_JDEFINES="${ARG_JDEFINES} -Dsccheck.severity=${OPTARG}"
			;;

		h)	# hostlist 
			if [[ ${CLUSTERMODE} == ${FALSE} ]]; then
			    printf "$(gettext '%s: hostlist not valid from non-cluster node')\n" "${PROG}" >&2
			    print_usage
			    exit_err ${ERR_USAGE}
			fi
			HOSTLIST=${OPTARG}
			ARG_JDEFINES="${ARG_JDEFINES} -Dsccheck.hostlist=${HOSTLIST}"
			hflg=${SC_TRUE}
			;;

		o)	# outputdir
			oflg=${SC_TRUE}
			OUTPUTDIR=${OPTARG}
			ARG_JDEFINES="${ARG_JDEFINES} -Dsccheck.outputDir=${OUTPUTDIR}"
			;;
		c)      # check listing to be run on single node mode, file existence is tested by common_check_prereqs
			SINGLE_NODE_CHECK_FILE_PATH=${OPTARG}
			ARG_JDEFINES="${ARG_JDEFINES} -Dsccheck.singleNodecheckFilePath=${SINGLE_NODE_CHECK_FILE_PATH}"
			;;
		C)      # check listing to be run on multi node mode, file existence is tested by common_check_prereqs
			MULTI_NODE_CHECK_FILE_PATH=${OPTARG}
			ARG_JDEFINES="${ARG_JDEFINES} -Dsccheck.multiNodeModecheckFilePath=${MULTI_NODE_CHECK_FILE_PATH}"
			Cflg=${SC_TRUE}
			;;
		e)      # explorer input files 
		        EXPLORER_INPUT_FILES=${OPTARG}
			ARG_JDEFINES="${ARG_JDEFINES} -Dsccheck.explorerFilesPath=${EXPLORER_INPUT_FILES}"
			;;
		\?)	# bad option
			print_usage
			exit_err ${ERR_USAGE}
			;;

		*)	# we missed something!
			printf "$(gettext '%s:  Internal error checking usage')\n" "${PROG}" >&2
			exit_err ${ERR_UNSPECIFIED}
			;;
		esac
	done
	shift $((OPTIND - 1))

	# Make sure there are no remaining args
	if [[ $# -ne 0 ]]; then
		print_usage
		exit_err ${ERR_USAGE}
	fi

	if [ ${hflg} = ${SC_TRUE} ] && [ ${Cflg} = ${SC_TRUE} ]; then
	    printf "$(gettext 'Sorry, -h and -C are mutually exclusive')\n" >&2
	    print_usage
	    exit_err ${ERR_USAGE}
	fi


} # read_args

##############################
#
#  load_cluster_data
#
#	input:		nothing
#	output:		nothing
#	returns:	nothing
#
#	action:	
#	    fetch cluster data:
#		clustername
#		clustermode
#		local nodename
#		current cluster members
#		    public & private names
#		setup java defines for passing data to jvm
#
#	no I18N: use C locale
#
##############################

load_cluster_data()
{
    typeset lc_save=${LC_ALL}
    export LC_ALL=C

    typeset clustername=
    CLUSTERMODE=$(common_get_clustermode)

    typeset localname=
    typeset nodelist=
    typeset private_names=

    if [[ ${CLUSTERMODE} == ${FALSE} ]]; then
	localname=$(/usr/bin/uname -n)
	private_localname=""
	clustername=${localname}
    else
	clustername=$(common_get_clustername)
	localname=$(${SCHA_CLGET} NODENAME_LOCAL)
	private_localname=$(${SCHA_CLGET} PRIVATELINK_HOSTNAME_NODE ${localname}) 
        nodelist="$(${SCHA_CLGET} ALL_NODENAMES)"

	for node in ${nodelist}
	do
	    nodestate=$(${SCHA_CLGET} NODESTATE_NODE ${node})
	    if [[ ${nodestate} = "UP" ]]; then # no I18N
		PUBLIC_NAMES="${PUBLIC_NAMES} ${node}"
		private_name=$(${SCHA_CLGET} PRIVATELINK_HOSTNAME_NODE ${node})
		private_names="${private_names} ${private_name}"
	    fi
	done

	# convert to comma separated list
	PUBLIC_NAMES=$(echo ${PUBLIC_NAMES} | tr " " ",")
	private_names=$(echo ${private_names} | tr " " ",")
    fi

    CLUSTER_JDEFINES="${CLUSTER_JDEFINES} -Dsccheck.clustername=${clustername}"
    CLUSTER_JDEFINES="${CLUSTER_JDEFINES} -Dsccheck.clustermode=${CLUSTERMODE}"
    CLUSTER_JDEFINES="${CLUSTER_JDEFINES} -Dsccheck.localname=${localname}"
    CLUSTER_JDEFINES="${CLUSTER_JDEFINES} -Dsccheck.privatelocalname=${private_localname}"
    CLUSTER_JDEFINES="${CLUSTER_JDEFINES} -Dsccheck.publicnodenames=${PUBLIC_NAMES}"
    CLUSTER_JDEFINES="${CLUSTER_JDEFINES} -Dsccheck.privatenodenames=${private_names}"
    
    export LC_ALL=${lc_save}
} # load_cluster_data

#####################################################
#
#  assign_defaults
#
#	input:		nothing
#	output:		nothing
#	returns:	nothing
#
#	action:		If user didn't assign an
#		output directory then setup the
#		default.
#
#####################################################

assign_defaults()
{
    if [[ -z ${OUTPUTDIR} ]]; then
	OUTPUTDIR=${SCCHECK_REPORTSDIR_CLIENT}
	ARG_JDEFINES="${ARG_JDEFINES} -Dsccheck.outputDir=${OUTPUTDIR}"
    fi
} # assign_defaults


#####################################################
#
# calc_num_nodes
#
#	input:		nothing
#	output:		prints integer
#	returns:	nothing
#
#	action:		Calculate number of nodes
#		that will be involved in this sccheck
#		run. It will be $HOSTLIST if the
#		user set it or the number of current
#		cluster members by default.
#
#####################################################

calc_num_nodes()
{
    if [[ ${#HOSTLIST} -gt 0 ]]; then
	echo ${HOSTLIST} | awk -F, '{print NF}'
    else
	echo ${PUBLIC_NAMES} | awk -F, '{print NF}'
    fi
} # calc_num_nodes

#####################################################
#
# pre_run_cleanup
#
#	input:		nothing
#	output:		nothing
#	returns:	nothing
#
#	action:		
#		Removes sccheck-private copies of
#	compressed explorers from prior runs to prevent
#	them from being sucked into next explorer results.
#
#	Executed only if there is only one current
#	client.
#
#####################################################

pre_run_cleanup()
{
    if [[ ${CLIENT_NUM} -eq 0 ]]; then

	# cleanup old sccheck explorer stuff only
	# if I'm the only client
	rm -rf ${SCCHECK_EXP_BASE}*/*
    fi
} # pre_run_cleanup


#####################################################
#
# make_dirs
#
#	input:		nothing
#	output:		nothing
#	returns:	0 for success or 1 if error
#
#	action:
#
#	Create the directories required for the
#	client using common_mkdir().
#
#	Create directories needed for both client and 
#	server using common_mkdirs().
#
#	Any error messages are emitted by service
#	routine 'common_mkdir'.
#
#####################################################

make_dirs()
{

    common_mkdir ${OUTPUTDIR}			|| return 1
    common_mkdir ${SCCHECK_EXPS_GZDIR}		|| return 1
    common_mkdir ${SCCHECK_EXPS_UPKDIR}		|| return 1
    common_mkdir ${SCCHECK_VARTMPDIR}		|| return 1
    common_mkdirs				|| return 1

    return 0
} # make_dirs

#####################################################
#
# age_client_logs
#
#	input:		nothing
#	output:		nothing
#	returns:	nothing
#
#	action: 
#
#	Perform aging of log files on client machine for
#	current client only.
#
#	Logs progress through four stages then disappear.
#	
#####################################################

age_client_logs()
{

    # client.log

    # .older -> .oldest
    LOG_TARGET=${SCCHECK_LOGDIR}/client.log.${CLIENT_NUM}.OLDER
    LOG_AGED=${SCCHECK_LOGDIR}/client.log.${CLIENT_NUM}.OLDEST
    if [[ -a ${LOG_TARGET} ]]; then
	/usr/bin/mv ${LOG_TARGET} ${LOG_AGED} 2> /dev/null
    fi

    # .old -> .older
    LOG_TARGET=${SCCHECK_LOGDIR}/client.log.${CLIENT_NUM}.OLD
    LOG_AGED=${SCCHECK_LOGDIR}/client.log.${CLIENT_NUM}.OLDER
    if [[ -a ${LOG_TARGET} ]]; then
	/usr/bin/mv ${LOG_TARGET} ${LOG_AGED} 2> /dev/null
    fi

    # log -> .old
    LOG_TARGET=${SCCHECK_LOGDIR}/client.log.${CLIENT_NUM}
    LOG_AGED=${SCCHECK_LOGDIR}/client.log.${CLIENT_NUM}.OLD
    if [[ -a ${LOG_TARGET} ]]; then
	/usr/bin/mv ${LOG_TARGET} ${LOG_AGED} 2> /dev/null
    fi

    # ke client-side log
    # .older -> .oldest
    LOG_TARGET=${SCCHECK_LOGDIR}/${KECLIENTLOG}.${CLIENT_NUM}.OLDER
    LOG_AGED=${SCCHECK_LOGDIR}/${KECLIENTLOG}.${CLIENT_NUM}.OLDEST
    if [[ -a ${LOG_TARGET} ]]; then
	/usr/bin/mv ${LOG_TARGET} ${LOG_AGED} 2> /dev/null
    fi

    # .old -> .older
    LOG_TARGET=${SCCHECK_LOGDIR}/${KECLIENTLOG}.${CLIENT_NUM}.OLD
    LOG_AGED=${SCCHECK_LOGDIR}/${KECLIENTLOG}.${CLIENT_NUM}.OLDER
    if [[ -a ${LOG_TARGET} ]]; then
	/usr/bin/mv ${LOG_TARGET} ${LOG_AGED} 2> /dev/null
    fi

    # log -> .old
    LOG_TARGET=${SCCHECK_LOGDIR}/${KECLIENTLOG}.${CLIENT_NUM}
    LOG_AGED=${SCCHECK_LOGDIR}/${KECLIENTLOG}.${CLIENT_NUM}.OLD
    if [[ -a ${LOG_TARGET} ]]; then
	/usr/bin/mv ${LOG_TARGET} ${LOG_AGED} 2> /dev/null
    fi

} # age_client_logs

#####################################################
#
# launched_by_explorer
#
#	Since sccheck will call explorer we can't allow
#	explorer to call us: no recursion allowed.
#
#	If the  module is running and is my parent then 
#	sccheck should refuse to run.
#
#	input:		nothing
#	output:		nothing
#	returns:	0 for OK to proceed or 1 if not
#
#	action: 
#
#	Look for the explorer module in the process
#	table; is it my parent process?
#
#
#####################################################

launched_by_explorer()
{
    # name of explorer module: no I18N
    typeset target_parent="tools/cluster"

    # look for module in process list
    # and check for my parent's ID on it
    /usr/bin/pgrep -f ${target_parent} | /usr/bin/grep ${PPID}
    typeset status=$?
    if [[ ${status} -eq 0 ]]; then # found: bad
	return 1
    else # not found: good
	return 0
    fi
    return $?
} # launched_by_explorer

#####################################################
#
# exit_err
#
#	input:		optional error code
#	output:		nothing
#	returns:	exits the shell
#
#	action: 
#
#	Exit the shell with a specified status code
#	after calling cleanup.
#
#####################################################

exit_err()
{
    if [ $# -ne 1 ]; then
	status=${ERR_UNSPECIFIED}
    else
	status=$1
    fi

    cleanup

    exit ${status}
} # exit_err

#####################################################
#
# cleanup
#
#	File cleanups performed before exiting:
#		pidfile for this client
#
#	input:		nothing
#	output:		nothing
#	returns:	nothing
#
#	action: 
#
#	File cleanups performed before exiting:
#		pidfile for this client
#
#####################################################

cleanup()
{
    /bin/rm -f ${SCCHECK_PIDFILE}
} # cleanup

#######################################
#
#  trap_exit
#
#	Signal handler.
#
#	Exits the shell.
#
#	input:		nothing
#	output:		nothing
#	returns:	exits the shell.
#
#	action: 
#		Attempt to kill the JVM spawned by this
#	client. Exit the shell with ${ERR_TRAP} exit code.
#
#######################################

trap_exit()
{
    printf "$(gettext '%s: Interrupted.')\n" "${PROG}" >&2
    if [[ ${JVM_PID} > 1 ]]; then

	# make sure that ${JVM_PID} points to real process
	# and that it's sccheck or sccheckd
	/usr/bin/pgrep -f "sccheck" | /usr/bin/grep ${JVM_PID} 2> /dev/null
	if [[ $? -eq 0 ]]; then
	    kill -15 ${JVM_PID}
	fi
    fi

    cleanup
    exit ${ERR_TRAP}
} # trap_exit

###################################################
#
#	end function definitions
#
###################################################



#####################################################
#
# "Main"
#
#	Exits the shell either: indirectly via exit_err
#	call or by a subroutine calling exit_err; or by
#	the explicit exit at the end of this section.
#
#	Exits with code set by ksh processing or
#	returned by the java client code.
#
#####################################################

trap 'trap_exit' HUP INT QUIT TERM


# validation routines may emit error messages: send to stderr
common_verify_isauthorized >&2 || exit_err ${ERR_NOT_ROOT}
common_verify_iseuidroot >&2 || exit_err ${ERR_NOT_ROOT}

# sccheck may not be run by explorer
launched_by_explorer >&2 || exit_err ${ERR_EXPL_PARENT}

# load up some environment
load_cluster_data

# process user input
# read_args() depends upon load_cluster_data()
read_args $*

set -A exist_files "\
	${SINGLE_NODE_CHECK_FILE_PATH} \
	${MULTI_NODE_CHECK_FILE_PATH} \
	${NON_CLUSTER_CHECK_FILE_PATH} \
    "

common_test_java_version >&2 || exit_err ${ERR_PREREQS}
common_check_prereqs >&2 || exit_err ${ERR_PREREQS}

assign_defaults

# Test for sufficient available diskspace as a
# function of number of nodes in hostlist,
# whether explicit or default
#
# calc_num_nodes() depends upon load_cluster_data()
# and read_args()
typeset num_nodes=$(calc_num_nodes)

# validation routines may emit error messages: send to stderr
common_test_diskspace ${num_nodes} >&2 || exit_err ${ERR_DISK_SPACE}
common_test_eke_version >&2 || exit_err ${ERR_INVALID_EKE}

# last pre-run preparations
make_dirs >&2 || exit_err ${ERR_FAILED_DIRS}
age_client_logs
pre_run_cleanup

# create my client pidfile
echo $$ > ${SCCHECK_PIDFILE}

#
# launch jvm in background so we can save pid of jvm:
# enables ^C kill of both ksh & java
#
typeset JDEFINES_LIST="${KE_JDEFINES} ${CLUSTER_JDEFINES} ${CLNUM_JDEFINE} ${ARG_JDEFINES}"

# echo "CLASSPATH: ${CLASSPATH}"

${JAVA} -classpath ${CLASSPATH} ${JDEFINES_LIST} ${JAVA_CLIENT} &
JVM_PID=$!
    
# now wait for java to exit
wait $JVM_PID
typeset -i exitstatus=$?

# constuct closing message if reports were created
if ((${exitstatus} > 0)) && ((${exitstatus} < 100)); then
    typeset severity=""
    printf ""
    case "${exitstatus}" in
	1)	severity=$(gettext '1 (LOW)');;
	2)	severity=$(gettext '2 (MODERATE)');;
	3)	severity=$(gettext '3 (HIGH)');;
	4)	severity=$(gettext '4 (CRITICAL)');;
    esac

    printf "$(gettext '%s: One or more checks failed.')\n" ${PROG}
    printf "$(gettext '%s: The greatest severity of all check failures was %s.')\n" ${PROG} "${severity}"
    printf "$(gettext '%s: Reports are in %s.')\n" ${PROG} ${OUTPUTDIR}
fi

cleanup

exit ${exitstatus}

