#!/bin/sh
#
# Copyright (c) 2013 Red Hat.
# Copyright (c) 2000-2008 Silicon Graphics, Inc.  All Rights Reserved.
# 
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.
# 
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
# for more details.
# 
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
# 
# Start or Stop the Performance Co-Pilot Collection Daemon (PMCD)
#
# The following is for chkconfig on RedHat based systems
# chkconfig: 2345 95 05
# description: pmcd is the collection daemon for the Performance Co-Pilot (PCP)
#
# The following is for insserv(1) based systems,
# e.g. SuSE, where chkconfig is a perl script.
### BEGIN INIT INFO
# Provides:          pmcd
# Required-Start:    $local_fs
# Should-Start:      $network $remote_fs $syslog $time
# Required-Stop:     $local_fs
# Should-Stop:       $network $remote_fs $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Control pmcd (the collection daemon for PCP)
# Description:       Configure and control pmcd (the collection daemon for the Performance Co-Pilot)
### END INIT INFO

. $PCP_DIR/etc/pcp.env
. $PCP_SHARE_DIR/lib/rc-proc.sh

PMCD=$PCP_BINADM_DIR/pmcd
PMCDOPTS=$PCP_PMCDOPTIONS_PATH
PCPLOCAL=$PCP_PMCDRCLOCAL_PATH
RUNDIR=$PCP_LOG_DIR/pmcd
prog=$PCP_RC_DIR/`basename $0`

# search for your mail agent of choice ...
#
MAIL=''
for try in Mail mail email
do
    if which $try >/dev/null 2>&1
    then
	MAIL=$try
	break
    fi
done

tmp=`mktemp -d /var/tmp/pcp.XXXXXXXXX` || exit 1
status=1
trap "rm -rf $tmp; exit \$status" 0 1 2 3 15

case "$PCP_PLATFORM"
in
    mingw)
	# nothing we can usefully do here, skip the test
	#
	IAM=0
	;;

    *)
	# standard Unix/Linux style test
	#
	ID=id
	IAM=`$ID -u 2>/dev/null`
	if [ -z "$IAM" ]
	then
	    # do it the hardway
	    #
	    IAM=`$ID | sed -e 's/.*uid=//' -e 's/(.*//'`
	fi
	;;
esac

_pmcd_logfile()
{
default=$RUNDIR/pmcd.log
$PCP_AWK_PROG <$PMCDOPTS '
BEGIN		{ logf = "'$default'" }
$1 == "-l"	{ if (NF > 1) logf = $2 }
END		{ print logf }'
}

_reboot_setup()
{
    # base directories and house-keeping for daemon processes
    #
    # For most packages, $PCP_RUN_DIR is included in the package,
    # but for Debian and cases where /var/run is a mounted filesystem
    # it may not exist, so create it here before it is used to create
    # any pid/lock files
    #
    # $PCP_RUN_DIR creation is also done in pmlogger_daily, but if pmcd
    # is running, we'd expect do this one first
    #
    if [ ! -d "$PCP_RUN_DIR" ]
    then
	mkdir -p -m 775 "$PCP_RUN_DIR"
	chown $PCP_USER:$PCP_GROUP "$PCP_RUN_DIR"
    fi

    # setup and clean up base directories and house-keeping for tracking
    # pmlogger instances ... needs to be done here because pmcd needs the
    # information, even if no pmlogger instances are started (and even if
    # $PCP_RC_DIR/pmlogger is not run)
    #
    if [ ! -d "$PCP_TMP_DIR/pmlogger" ]
    then
	mkdir -p -m 1777 "$PCP_TMP_DIR/pmlogger"
    else
	rm -rf $tmp/ent $tmp/pid
	here=`pwd`
	cd "$PCP_TMP_DIR/pmlogger"
	rm -f primary vcr
	_get_pids_by_name pmlogger | sort >$tmp/pid
	ls [0-9]* 2>&1 | sed -e '/\[0-9]\*/d' \
	| sed -e 's/[ 	][ 	]*//g' | sort >$tmp/ent
	# remove entries without a pmlogger process
	#
	rm -f `comm -23 $tmp/ent $tmp/pid`
	rm -f $tmp/ent $tmp/pid
	cd "$here"
    fi

    # base directory for Memory Mapped Value PMDA and clients
    #
    if [ ! -d "$PCP_TMP_DIR/mmv" ]
    then
	mkdir -p -m 1777 "$PCP_TMP_DIR/mmv"
    fi

    # Rebuild PMNS?
    #
    PMNSDIR=$PCP_VAR_DIR/pmns

    rebuild=false
    if [ -d $PMNSDIR -a \( -f $PMNSDIR/.NeedRebuild -o ! -f $PMNSDIR/root \) ]
    then
	rebuild=true
    else
	num=`find $PMNSDIR -newer $PMNSDIR/root -iname 'root_*' 2>/dev/null | wc -l`
	[ "$num" -gt 0 ] && rebuild=true
    fi

    if $rebuild
    then
	if [ -x $PMNSDIR/Rebuild ]
	then
	    $ECHO $PCP_ECHO_N "Rebuilding PMNS ..." "$PCP_ECHO_C"
	    here=`pwd`
	    cd $PMNSDIR
	    ./Rebuild -du $REBUILDOPT
	    $RC_STATUS -v
	    # The 'root' file does not get updated when data did not change,
	    # so we must touch it to update date.
	    [ $? -eq 0 ] && { rm -f .NeedRebuild; touch root; }
	    cd "$here"
	fi
    fi
}

_pmda_setup()
{
    # Auto-Install PMDAs?
    #
    if [ -d $PCP_PMDAS_DIR ]
    then
	here=`pwd`
	cd $PCP_PMDAS_DIR
	for file in */.NeedInstall
	do
	    [ "$file" = '*/.NeedInstall' ] && break
	    pmda=`dirname $file`
	    if [ -d "$pmda" -a -f "$pmda/.NeedInstall" ]
	    then
		cd $pmda
		$PCP_ECHO_PROG "Installing $pmda PMDA ..."

		# rename .NeedInstall _before_ calling Install because
		# Install can call this start script (recursively) and
		# we don't want to get stuck in an infinite loop.
		# 
		rm -f .NeedInstall.sav
		mv .NeedInstall .NeedInstall.sav
		if ./Install </dev/null >/dev/null
		then
		    # success
		    pmpost "PMDA setup: automated install: $pmda"
		    rm -f .NeedInstall.sav
		else
		    # put the file back, maybe we'll be luckier next time
		    pmpost "PMDA setup: automated install FAILED (exit=$?): $pmda"
		    mv .NeedInstall.sav .NeedInstall
		fi

		cd $PCP_PMDAS_DIR
	    fi
	done
	cd "$here"
    fi
}

_start_pmcheck()
{
    if [ ! -z "$PMCD_WAIT_TIMEOUT" ]
    then
	wait_option="-t $PMCD_WAIT_TIMEOUT" 
    else 
	wait_option=''
    fi

    if pmcd_wait $wait_option
    then
	:
    else
	status=$?
	pmpost "pmcd_wait failed in $prog: exit status: $status" 
	if [ ! -z "$MAIL" ]
	then
	    echo "pmcd_wait exit status: $status" | $MAIL -s "pmcd_wait failed in $prog" root
	else
	    echo "$prog: pmcd_wait failed: exit status: $status"
	fi
    fi
}

# Use $PCP_PMCDCONF_PATH to find and terminate pipe/socket PMDAs.
# (First join up continued lines in config file)
#
_killpmdas()
{
    if [ ! -f $PCP_PMCDCONF_PATH ]
    then
	echo "$prog:"'
Warning: pmcd control file '"$PCP_PMCDCONF_PATH"' is missing, cannot identify PMDAs
         to be terminated.'
	return
    fi
    # Give each PMDA 2 seconds after a SIGTERM to die, then SIGKILL
    for pmda in `$PCP_AWK_PROG <$PCP_PMCDCONF_PATH '
/\\\\$/		{ printf "%s ", substr($0, 0, length($0) - 1); next }
		{ print }' \
| $PCP_AWK_PROG '
$1 ~ /^#/				{ next }
tolower($3) == "pipe" && NF > 4		{ print $5; next }
tolower($3) == "socket" && NF > 5	{ print $6; next }' \
| sort -u`
    do
	pmsignal -a -s TERM `basename $pmda` > /dev/null 2>&1 &
    done
    sleep 2
    for pmda in `$PCP_AWK_PROG <$PCP_PMCDCONF_PATH '
/\\\\$/		{ printf "%s ", substr($0, 0, length($0) - 1); next }
		{ print }' \
| $PCP_AWK_PROG '
$1 ~ /^#/				{ next }
tolower($3) == "pipe" && NF > 4		{ print $5; next }
tolower($3) == "socket" && NF > 5	{ print $6; next }' \
| sort -u`
    do
	pmsignal -a -s KILL `basename $pmda` > /dev/null 2>&1 &
    done

    wait
}

_shutdown()
{
    # Is pmcd running?
    #
    _get_pids_by_name pmcd >$tmp/tmp
    if [ ! -s $tmp/tmp ]
    then
 	[ "$1" = verbose ] && echo "$prog: pmcd not running"
	rm -f $PCP_RUN_DIR/pmcd.pid $PCP_RUN_DIR/pmcd.socket
	return 0
    fi

    # If pmcd is running but we can't find a pidfile, or a logfile at the
    # configured or default location, assume this script is being run via
    # a chroot build environment (and hence we do not want to signal pmcd).
    #
    logf=`_pmcd_logfile`
    [ -f $logf ] || logf=$RUNDIR/pmcd.log
    if [ ! -f $PCP_RUN_DIR/pmcd.pid -a ! -f $logf ]
    then
	pmcdpid=`cat $tmp/tmp`
	echo "PMCD process ... $pmcdpid"
        echo "$prog:
Warning: found no $PCP_RUN_DIR/pmcd.pid
         and no $logf.
         Assuming an uninstall from a chroot: pmcd not killed.
         If this is incorrect, \"pmsignal -s TERM $pmcdpid\"  can be used."
        exit
    elif [ -f $PCP_RUN_DIR/pmcd.pid ]
    then
	TOKILL=`cat $PCP_RUN_DIR/pmcd.pid`
	if grep "^$TOKILL$" $tmp/tmp >/dev/null
	then
	    :
	else
	    echo "PMCD process ... "`cat $tmp/tmp`
	    echo "$prog:
Warning: process ID in $PCP_RUN_DIR/pmcd.pid is $TOKILL.
         Check logfile $logf. When you are ready to proceed, remove 
         $PCP_RUN_DIR/pmcd.pid before retrying."
	    exit
	fi
    else
	TOKILL=
    fi

    # Send pmcd a SIGTERM, which is noted as a pending shutdown.
    # When finished the currently active request, pmcd will close any
    # connections, wait for any agents, and then exit.
    # On failure, resort to SIGKILL.
    #
    $ECHO $PCP_ECHO_N "Waiting for pmcd to terminate ...""$PCP_ECHO_C"
    delay=200	# tenths of a second
    for SIG in TERM KILL
    do
	if [ "x$TOKILL" = "x" ]
	then
	    pmsignal -a -s $SIG pmcd > /dev/null 2>&1
	else
	    pmsignal -s $SIG $TOKILL >/dev/null 2>&1
	    rm -f $PCP_RUN_DIR/pmcd.pid $PCP_RUN_DIR/pmcd.socket
	fi
	while [ $delay -gt 0 ]
	do
	    _get_pids_by_name pmcd >$tmp/tmp
	    [ ! -s $tmp/tmp ] && break 2
	    pmsleep 0.1
	    delay=`expr $delay - 1`
	    [ "$SIG" = "TERM" ] && [ `expr $delay % 10` -eq 0 ] \
		&& $ECHO $PCP_ECHO_N ".""$PCP_ECHO_C"
	done
	echo
	echo "Process ..."
	if [ "$SIG" = "TERM" ]
	then
	    $PCP_PS_PROG $PCP_PS_ALL_FLAGS >$tmp/ps
	    sed 1q $tmp/ps
	    for pid in `cat $tmp/tmp`
	    do
	        $PCP_AWK_PROG <$tmp/ps "\$2 == $pid { print }"
	    done
	    echo "$prog: Warning: Forcing pmcd to terminate!"
	    delay=20
	else
	    cat $tmp/tmp
	    echo "$prog: Warning: pmcd won't die!"
	    exit
	fi
    done
    _killpmdas
    $RC_STATUS -v
    pmpost "stop pmcd from $prog"
}

_usage()
{
    echo "Usage: $prog [-v] {start|restart|condrestart|stop|status|reload|force-reload}"
}

VERBOSE_CTL=on
while getopts v c
do
    case $c
    in
	v)  # force verbose ... for historical reasons only as $VERBOSE_CTL
	    # is always "on"
	    ;;
	
	*)
	    _usage
	    exit 1
	    ;;
    esac
done
shift `expr $OPTIND - 1`

if [ $VERBOSE_CTL = on ]
then				# For a verbose startup and shutdown
    ECHO=$PCP_ECHO_PROG
    REBUILDOPT=''
    VFLAG='-v'
else				# For a quiet startup and shutdown
    ECHO=:
    REBUILDOPT=-s
    VFLAG=
fi

if [ "$IAM" != 0 -a "$1" != "status" ]
then
    if [ -n "$PCP_DIR" ]
    then
	: running in a non-default installation, do not need to be root
    else
	echo "$prog:"'
Error: You must be root (uid 0) to start or stop the Performance Co-Pilot pmcd.'
	exit
    fi
fi

# First reset status of this service
$RC_RESET
 
# Return values acc. to LSB for all commands but status:
# 0 - success
# 1 - misc error
# 2 - invalid or excess args
# 3 - unimplemented feature (e.g. reload)
# 4 - insufficient privilege
# 5 - program not installed
# 6 - program not configured
#
# Note that starting an already running service, stopping
# or restarting a not-running service as well as the restart
# with force-reload (in case signalling is not supported) are
# considered a success.
case "$1" in

  'start'|'restart'|'condrestart'|'reload'|'force-reload')
	if [ "$1" = "condrestart" ] && ! is_chkconfig_on pmcd
	then
	    status=0
	    exit
	fi
	_shutdown quietly

	# Clean the environment for PMCD:
	# PMCD and PMDA messages should go to stderr, not the GUI notifiers
	# Clients in these scripts should test PMCD status without TLS/SSL.
	#
	unset PCP_STDERR PCP_SECURE_SOCKETS

	_reboot_setup

	if [ -x $PMCD ]
	then
	    if [ ! -f $PCP_PMCDCONF_PATH ]
	    then
		echo "$prog:"'
Error: pmcd control file '"$PCP_PMCDCONF_PATH"' is missing, cannot start pmcd.'
		exit
	    fi
	    [ ! -d "$RUNDIR" ] && mkdir -p "$RUNDIR" && chown pcp:pcp "$RUNDIR"
	    cd "$RUNDIR"

	    # salvage the previous versions of any PMCD and PMDA logfiles
	    #
	    for log in pmcd `sed -e '/^#/d' -e '/\[access\]/q' -e 's/[ 	].*//' <$PCP_PMCDCONF_PATH`
	    do
		if [ -f $log.log ]
		then
		    rm -f $log.log.prev
		    mv $log.log $log.log.prev
		fi
	    done

	    $ECHO $PCP_ECHO_N "Starting pmcd ..." "$PCP_ECHO_C"

	    # only consider lines which start with a hyphen
	    # get rid of the -f option
	    # ensure multiple lines concat onto 1 line
	    OPTS=`sed <$PMCDOPTS 2>/dev/null \
			    -e '/^[^-]/d' \
			    -e 's/^/ /' \
			    -e 's/$/ /' \
			    -e 's/ -f / /g' \
			    -e 's/^ //' \
			    -e 's/ $//' \
		    | tr '\012' ' ' `

	    $PMCD $OPTS
	    $RC_STATUS -v

	    pmpost "start pmcd from $prog"

	    _pmda_setup

	    # force removal of primary pmlogger link ... if primary
	    # pmlogger is started, this will re-create the link
	    #
	    rm -f $PCP_TMP_DIR/pmlogger/primary

	    # site-local customisations after PMCD startup
	    #
	    [ -x $PCPLOCAL ] && $PCPLOCAL $VFLAG start
	    
	fi
	status=0
        ;;

  'stop')
	# site-local customisations before pmcd shutdown
	#
	[ -x $PCPLOCAL ] && $PCPLOCAL $VFLAG stop
	_shutdown verbose
	status=0
        ;;

  'status')
	# NOTE: $RC_CHECKPROC returns LSB compliant status values.
	$ECHO $PCP_ECHO_N "Checking for pmcd:" "$PCP_ECHO_C"
        if [ -r /etc/rc.status ]
        then
            # SuSE
            $RC_CHECKPROC $PMCD
            $RC_STATUS -v
            status=$?
        else
            # not SuSE
            $RC_CHECKPROC $PMCD
            status=$?
            if [ $status -eq 0 ]
            then
                $ECHO running
            else
                $ECHO stopped
            fi
        fi
	;;

  *)
	_usage
        ;;
esac

