diff options
Diffstat (limited to 'src/pmcd')
-rw-r--r-- | src/pmcd/GNUmakefile | 52 | ||||
-rw-r--r-- | src/pmcd/pmcd.options | 36 | ||||
-rw-r--r-- | src/pmcd/pmcd.service | 14 | ||||
-rw-r--r-- | src/pmcd/pmcd.service.in | 14 | ||||
-rw-r--r-- | src/pmcd/pmdaproc.sh | 1457 | ||||
-rw-r--r-- | src/pmcd/rc-proc.sh | 394 | ||||
-rw-r--r-- | src/pmcd/rc-proc.sh.minimal | 74 | ||||
-rw-r--r-- | src/pmcd/rc_local | 67 | ||||
-rw-r--r-- | src/pmcd/rc_pcp | 76 | ||||
-rw-r--r-- | src/pmcd/rc_pmcd | 540 | ||||
-rw-r--r-- | src/pmcd/sasl2.conf | 19 | ||||
-rw-r--r-- | src/pmcd/src/GNUmakefile | 38 | ||||
-rw-r--r-- | src/pmcd/src/agent.c | 221 | ||||
-rw-r--r-- | src/pmcd/src/client.c | 265 | ||||
-rw-r--r-- | src/pmcd/src/client.h | 58 | ||||
-rw-r--r-- | src/pmcd/src/config.c | 2526 | ||||
-rw-r--r-- | src/pmcd/src/dofetch.c | 582 | ||||
-rw-r--r-- | src/pmcd/src/dopdus.c | 1057 | ||||
-rw-r--r-- | src/pmcd/src/dostore.c | 312 | ||||
-rw-r--r-- | src/pmcd/src/pmcd.c | 1024 | ||||
-rw-r--r-- | src/pmcd/src/pmcd.h | 233 | ||||
-rw-r--r-- | src/pmcd/src/util.c | 96 |
22 files changed, 9155 insertions, 0 deletions
diff --git a/src/pmcd/GNUmakefile b/src/pmcd/GNUmakefile new file mode 100644 index 0000000..6370205 --- /dev/null +++ b/src/pmcd/GNUmakefile @@ -0,0 +1,52 @@ +# +# Copyright (c) 2013-2014 Red Hat. +# Copyright (c) 2000,2004 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. +# + +TOPDIR = ../.. +include $(TOPDIR)/src/include/builddefs + +SUBDIRS = src +LDIRT = *.log + +default : $(SUBDIRS) pmcd.service + $(SUBDIRS_MAKERULE) + +install : $(SUBDIRS) + $(SUBDIRS_MAKERULE) + $(INSTALL) -m 755 -d `dirname $(PCP_PMCDOPTIONS_PATH)` + $(INSTALL) -m 644 pmcd.options $(PCP_PMCDOPTIONS_PATH) + $(INSTALL) -m 755 -d `dirname $(PCP_PMCDRCLOCAL_PATH)` + $(INSTALL) -m 755 rc_local $(PCP_PMCDRCLOCAL_PATH) + $(INSTALL) -m 755 rc_pmcd $(PCP_RC_DIR)/pmcd + $(INSTALL) -m 755 rc_pcp $(PCP_RC_DIR)/pcp +ifeq ($(ENABLE_SYSTEMD),true) + $(INSTALL) -m 644 pmcd.service $(PCP_SYSTEMDUNIT_DIR)/pmcd.service +endif + $(INSTALL) -m 644 pmdaproc.sh $(PCP_SHARE_DIR)/lib/pmdaproc.sh + $(INSTALL) -m 644 rc-proc.sh $(PCP_SHARE_DIR)/lib/rc-proc.sh + $(INSTALL) -m 644 rc-proc.sh.minimal $(PCP_SHARE_DIR)/lib/rc-proc.sh.minimal + $(INSTALL) -o $(PCP_USER) -g $(PCP_GROUP) -m 775 -d $(PCP_LOG_DIR)/pmcd +ifneq ($(PACKAGE_DISTRIBUTION),debian) + $(INSTALL) -m 755 -d $(PCP_SYSCONF_DIR)/pmcd +endif + $(INSTALL) -m 644 sasl2.conf $(PCP_SASLCONF_DIR)/pmcd.conf + +include $(BUILDRULES) + +default_pcp : default + +install_pcp : install + +pmcd.service: pmcd.service.in + $(SED) -e 's;@path@;'$(PCP_RC_DIR)';' $< > $@ diff --git a/src/pmcd/pmcd.options b/src/pmcd/pmcd.options new file mode 100644 index 0000000..bcd501e --- /dev/null +++ b/src/pmcd/pmcd.options @@ -0,0 +1,36 @@ +# command-line options to pmcd, uncomment/edit lines as required + +# listen for connections to pmcd on only the interface bound to this +# IP address +# -i 192.168.0.100 + +# longer timeout delay for slow agents +# -t 10 + +# or suppress timeouts +# -t 0 + +# make log go someplace else +# -l /some/place/else + +# debugging knobs, see pmdbg(1) +# -D fetch,pmns + +# run in the foreground (not as a daemon) +# -f + +# maximum incoming PDU size (default 64KB) +# -L 16384 + +# assume identity of some user other than "pcp" +# -U root + +# enable event tracing bit fields +# 1 trace client connections +# 2 trace PDUs +# 256 unbuffered tracing +# -T 3 + +# setting of environment variables for pmcd and +# the PCP rc scripts. See pmcd(1) and PMAPI(3). +# PMCD_WAIT_TIMEOUT=120 diff --git a/src/pmcd/pmcd.service b/src/pmcd/pmcd.service new file mode 100644 index 0000000..aea489d --- /dev/null +++ b/src/pmcd/pmcd.service @@ -0,0 +1,14 @@ +[Unit] +Description=Performance Metrics Collector Daemon +Documentation=man:pmcd(8) +Wants=avahi-daemon.service +After=local-fs.target network.target avahi-daemon.service + +[Service] +Type=oneshot +ExecStart=/etc/init.d/pmcd start +ExecStop=/etc/init.d/pmcd stop +RemainAfterExit=yes + +[Install] +WantedBy=multi-user.target diff --git a/src/pmcd/pmcd.service.in b/src/pmcd/pmcd.service.in new file mode 100644 index 0000000..8da7798 --- /dev/null +++ b/src/pmcd/pmcd.service.in @@ -0,0 +1,14 @@ +[Unit] +Description=Performance Metrics Collector Daemon +Documentation=man:pmcd(8) +Wants=avahi-daemon.service +After=local-fs.target network.target avahi-daemon.service + +[Service] +Type=oneshot +ExecStart=@path@/pmcd start +ExecStop=@path@/pmcd stop +RemainAfterExit=yes + +[Install] +WantedBy=multi-user.target diff --git a/src/pmcd/pmdaproc.sh b/src/pmcd/pmdaproc.sh new file mode 100644 index 0000000..0ef6eae --- /dev/null +++ b/src/pmcd/pmdaproc.sh @@ -0,0 +1,1457 @@ +# Common sh(1) procedures to be used in the Performance Co-Pilot +# PMDA Install and Remove scripts +# +# Copyright (c) 1995-2001,2003 Silicon Graphics, Inc. All Rights Reserved. +# Portions Copyright (c) 2008 Aconex. All Rights Reserved. +# Portions Copyright (c) 2013-2014 Red Hat. +# +# 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. + +# source the PCP configuration environment variables +. $PCP_DIR/etc/pcp.env + +tmp=`mktemp -d $PCP_TMPFILE_DIR/pmdaproc.XXXXXXXXX` || exit 1 +pmdatmp=$tmp +trap "rm -rf $pmdatmp $pcptmp; exit" 0 1 2 3 15 + +_setup_platform() +{ + case "$PCP_PLATFORM" + in + mingw) + uid=0 # no permissions we can usefully test here + dso_suffix=dll + default_pipe_opt=false + default_socket_opt=true + CHOWN=": skip chown" + CHMOD=chmod + ;; + *) + eval `id | sed -e 's/(.*//'` + dso_suffix=so + [ "$PCP_PLATFORM" = darwin ] && dso_suffix=dylib + default_pipe_opt=true + default_socket_opt=false + CHOWN=chown + CHMOD=chmod + ;; + esac +} + +_setup_localhost() +{ + # Try to catch some truly evil preconditions. If you cannot reach + # localhost, all bets are off! + # + if which ping >/dev/null 2>&1 + then + __opt='' + # Larry Wall style hunt for the version of ping that is first + # on our path + # + ping --help >$tmp/hlp 2>&1 + if grep '.-c count' $tmp/hlp >/dev/null 2>&1 + then + __opt='-c 1 localhost' + elif grep '.-n count' $tmp/hlp >/dev/null 2>&1 + then + __opt='-n 1 localhost' + elif grep 'host .*packetsize .*count' $tmp/hlp >/dev/null 2>&1 + then + __opt='localhost 56 1' + elif grep 'host .*data_size.*npackets' $tmp/hlp >/dev/null 2>&1 + then + __opt='localhost 56 1' + fi + if [ -z "$__opt" ] + then + echo "Warning: can't find a ping(1) that I understand ... pushing on" + else + if ping $__opt >$tmp/hlp 2>&1 + then + : + else + # failing that, try 3 pings ... failure means all 3 were lost, + # and so there is no hope of continuing + # + __opt=`echo "$__opt" | sed -e 's/1/3/'` + if ping $__opt >/dev/null 2>&1 + then + : + else + echo "Error: no route to localhost, pmcd reconfiguration abandoned" + exit 1 + fi + fi + fi + fi +} + +_setup_localhost +_setup_platform +rm -rf $tmp + +# some useful common variables for Install/Remove scripts +# +# put your PMNS files here +PMNSDIR=$PCP_VAR_DIR/pmns + +# pmcd and pcp log files here +if [ ! -z "$PCP_LOGDIR" ] +then + # this is being discouraged and is no longer documented anywhere + LOGDIR=$PCP_LOGDIR +else + if [ -d $PCP_LOG_DIR/pmcd ] + then + # the preferred naming scheme + # + LOGDIR=$PCP_LOG_DIR/pmcd + else + # backwards compatibility for IRIX + # + LOGDIR=$PCP_LOG_DIR + fi +fi + +# writeable root of PMNS +NAMESPACE=${PMNS_DEFAULT-$PMNSDIR/root} +PMNSROOT=`basename $NAMESPACE` + +# echo without newline - deprecated - use the $PCP_ECHO_* ones from +# /etc/pcp.conf instead, however some old Install and Remove scripts may +# still use $ECHONL, so keep it here +# +ECHONL="echo -n" + +# Install control variables +# Can install as DSO? +dso_opt=false +# Can install as perl script? +perl_opt=false +# Can install as python script? +python_opt=false +# Can install as daemon? +daemon_opt=true +# If daemon, pipe? +pipe_opt=$default_pipe_opt +# If daemon, socket? and default for Internet sockets? +socket_opt=$default_socket_opt +socket_inet_def='' +# IPC Protocol for daemon (binary only now) +ipc_prot=binary +# Need to force a restart of pmcd? +forced_restart=true +# Delay after install before checking (sec) +check_delay=3 +# Additional command line args to go in $PCP_PMCDCONF_PATH +args="" +# ditto for perl PMDAs +perl_args="" +# ditto for python PMDAS +python_args="" +# Source for the pmns +pmns_source=pmns +# Source for the helptext +help_source=help +# Assume libpcp_pmda.so.1 +pmda_interface=1 +# Full pathname to directory where PMDA is to be found ... +# exectable and/or DSO, domain.h, pmns, control files, etc. +pmda_dir="`pwd`" + + +# Other variables and constants +# +prog=`basename $0` +tmp=`mktemp -d $PCP_TMPFILE_DIR/pcp.XXXXXXXXX` || exit 1 +pcptmp=$tmp +do_pmda=true +do_check=true +__here=`pwd` +__pmcd_is_dead=false +__echo=false +__verbose=false +__ns_opt='' + +trap "rm -rf $tmp; exit" 0 1 2 3 15 + +# Parse command line args +# +while [ $# -gt 0 ] +do + case $1 + in + -e) # echo user input + __echo=true + ;; + + -N) # name space only + do_pmda=false + ;; + + -n) # alternate name space + if [ $# -lt 2 ] + then + echo "$prog: -n requires a name space file option" + exit 1 + fi + NAMESPACE=$2 + PMNSROOT=`basename $NAMESPACE` + PMNSDIR=`dirname $NAMESPACE` + __ns_opt="-n $2" + shift + ;; + + -Q) # skip check for metrics going away + do_check=false + ;; + + -R) # $ROOT + if [ "$prog" = "Remove" ] + then + echo "Usage: $prog [-eNQV] [-n namespace]" + exit 1 + fi + if [ $# -lt 2 ] + then + echo "$prog: -R requires a directory option" + exit 1 + fi + root=$2 + shift + ;; + + -V) # verbose + __verbose=true + ;; + + *) + if [ "$prog" = "Install" ] + then + echo "Usage: $prog [-eNQV] [-n namespace] [-R rootdir]" + else + echo "Usage: $prog [-eNQV] [-n namespace]" + fi + exit 1 + ;; + esac + shift +done + +# wait for pmcd to be alive again +# Usage: __wait_for_pmcd [can_wait] +# +__wait_for_pmcd() +{ + # 60 seconds default seems like a reasonble max time to get going + [ -z "$__can_wait" ] && __can_wait=${1-60} + if pmcd_wait -t $__can_wait + then + : + else + echo "Arrgghhh ... PMCD failed to start after $__can_wait seconds" + if [ -f $LOGDIR/pmcd.log ] + then + echo "Here is the PMCD logfile ($LOGDIR/pmcd.log):" + ls -l $LOGDIR/pmcd.log + cat $LOGDIR/pmcd.log + else + echo "No trace of the PMCD logfile ($LOGDIR/pmcd.log)!" + fi + __pmcd_is_dead=true + fi +} + +# try and put pmcd back the way it was +# +__restore_pmcd() +{ + if [ -f $tmp/pmcd.conf.save ] + then + __pmcd_is_dead=false + echo + echo "Save current PMCD control file in $PCP_PMCDCONF_PATH.prev ..." + rm -f $PCP_PMCDCONF_PATH.prev + mv $PCP_PMCDCONF_PATH $PCP_PMCDCONF_PATH.prev + echo "Restoring previous PMCD control file, and trying to restart PMCD ..." + cp $tmp/pmcd.conf.save $PCP_PMCDCONF_PATH + eval $CHOWN root $PCP_PMCDCONF_PATH + eval $CHMOD 644 $PCP_PMCDCONF_PATH + rm -f $tmp/pmcd.conf.save + $PCP_RC_DIR/pcp start + __wait_for_pmcd + fi + if $__pmcd_is_dead + then + echo + echo "Sorry, failed to restart PMCD." + fi +} + +# __pmda_cull name domain +# +__pmda_cull() +{ + # context and integrity checks + # + if [ $# -ne 2 ] + then + echo "pmdaproc.sh: internal botch: __pmda_cull() called with $# (instead of 2) arguments" + exit 1 + fi + [ ! -f $PCP_PMCDCONF_PATH ] && return + if eval $CHMOD u+w $PCP_PMCDCONF_PATH + then + : + else + echo "pmdaproc.sh: __pmda_cull: Unable to make $PCP_PMCDCONF_PATH writable" + exit 1 + fi + if [ ! -w $PCP_PMCDCONF_PATH ] + then + echo "pmdaproc.sh: \"$PCP_PMCDCONF_PATH\" is not writeable" + exit 1 + fi + + # remove matching entry from $PCP_PMCDCONF_PATH if present + # + $PCP_AWK_PROG <$PCP_PMCDCONF_PATH >$tmp/pmcd.conf ' +BEGIN { status = 0 } +$1 == "'"$1"'" && $2 == "'"$2"'" { status = 1; next } + { print } +END { exit status }' + if [ $? -eq 0 ] + then + # no match + : + else + + # log change to the PCP NOTICES file + # + $PCP_BINADM_DIR/pmpost "PMDA cull: from $PCP_PMCDCONF_PATH: $1 $2" + + # save pmcd.conf in case we encounter a problem, and then + # install updated $PCP_PMCDCONF_PATH + # + cp $PCP_PMCDCONF_PATH $tmp/pmcd.conf.save + cp $tmp/pmcd.conf $PCP_PMCDCONF_PATH + eval $CHOWN root $PCP_PMCDCONF_PATH + eval $CHMOD 644 $PCP_PMCDCONF_PATH + + # signal pmcd if it is running + # + if pminfo -v pmcd.version >/dev/null 2>&1 + then + pmsignal -a -s HUP pmcd >/dev/null 2>&1 + # allow signal processing to be done before checking status + sleep 2 + __wait_for_pmcd + if $__pmcd_is_dead + then + __restore_pmcd + # give PMCD a chance to get back into original state + sleep 3 + __wait_for_pmcd + fi + fi + fi + rm -f $tmp/pmcd.conf + + # stop any matching PMDA that is still running + # + for __sig in TERM KILL + do + __pids=`_get_pids_by_name pmda$1` + if [ ! -z "$__pids" ] + then + pmsignal -s $__sig $__pids >/dev/null 2>&1 + # allow signal processing to be done + sleep 2 + else + break + fi + done +} + +# __pmda_add "entry for $PCP_PMCDCONF_PATH" +# +__pmda_add() +{ + # context and integrity checks + # + if [ $# -ne 1 ] + then + echo "pmdaproc.sh: internal botch: __pmda_add() called with $# (instead of 1) arguments" + exit 1 + fi + if eval $CHMOD u+w $PCP_PMCDCONF_PATH + then + : + else + echo "pmdaproc.sh: __pmda_add: Unable to make $PCP_PMCDCONF_PATH writable" + exit 1 + fi + if [ ! -w $PCP_PMCDCONF_PATH ] + then + echo "pmdaproc.sh: \"$PCP_PMCDCONF_PATH\" is not writeable" + exit 1 + fi + + # save pmcd.conf in case we encounter a problem + # + cp $PCP_PMCDCONF_PATH $tmp/pmcd.conf.save + + myname=`echo $1 | $PCP_AWK_PROG '{print $1}'` + mydomain=`echo $1 | $PCP_AWK_PROG '{print $2}'` + # add entry to $PCP_PMCDCONF_PATH + # + echo >$tmp/pmcd.body + echo >$tmp/pmcd.access + $PCP_AWK_PROG <$PCP_PMCDCONF_PATH ' +NF==0 { next } +/^[ ]*\[[ ]*access[ ]*\]/ { state = 2 } +state == 2 { print >"'$tmp/pmcd.access'"; next } +$1=="'$myname'" && $2=="'$mydomain'" { next } + { print >"'$tmp/pmcd.body'"; next }' + ( cat $tmp/pmcd.body \ + ; echo "$1" \ + ; cat $tmp/pmcd.access \ + ) >$PCP_PMCDCONF_PATH + rm -f $tmp/pmcd.access $tmp/pmcd.body + eval $CHOWN root $PCP_PMCDCONF_PATH + eval $CHMOD 644 $PCP_PMCDCONF_PATH + + # log change to pcplog/NOTICES + # + $PCP_BINADM_DIR/pmpost "PMDA add: to $PCP_PMCDCONF_PATH: $1" + + # signal pmcd if it is running (and ok to do so), else start it + # + if ! $forced_restart && pminfo -v pmcd.version >/dev/null 2>&1 + then + pmsignal -a -s HUP pmcd >/dev/null 2>&1 + # allow signal processing to be done before checking status + sleep 2 + __wait_for_pmcd + $__pmcd_is_dead && __restore_pmcd + else + log=$LOGDIR/pmcd.log + rm -f $log + $PCP_RC_DIR/pcp start + __wait_for_pmcd + $__pmcd_is_dead && __restore_pmcd + fi +} + +# expect -R root or $ROOT not set in environment +# +__check_root() +{ + if [ "X$root" != X ] + then + ROOT="$root" + export ROOT + else + if [ "X$ROOT" != X -a "X$ROOT" != X/ ] + then + echo "Install: \$ROOT was set to \"$ROOT\"" + echo " Use -R rootdir to install somewhere other than /" + exit 1 + fi + fi +} + +# should be able to extract default domain from domain.h +# +__check_domain() +{ + if [ -f domain.h ] + then + __infile=domain.h + elif [ -f domain.h.perl ] + then + __infile=domain.h.perl + elif [ -f domain.h.python ] + then + __infile=domain.h.python + else + echo "Install: cannot find ./domain.h to determine the Performance Metrics Domain" + exit 1 + fi + # $domain is for backwards compatibility, modern PMDAs + # have something like + # #define FOO 123 + # + domain='' + eval `$PCP_AWK_PROG <$__infile ' +/^#define/ && $3 ~ /^[0-9][0-9]*$/ { print $2 "=" $3 + if (seen == 0) { + print "domain=" $3 + sub(/^PMDA/, "", $2) + print "SYMDOM=" $2 + seen = 1 + } + }'` + if [ "X$domain" = X ] + then + echo "Install: cannot determine the Performance Metrics Domain from ./domain.h" + exit 1 + fi +} + +# handle optional configuration files that maybe already given in an +# $PCP_PMCDCONF_PATH line or user-supplied or some default or sample +# file +# +# before calling _choose_configfile, optionally define the following +# variables +# +# Name Default Use +# +# configdir $PCP_VAR_DIR/config/$iam directory for config ... assumed +# name is $iam.conf in this directory +# +# configfile "" set if have a preferred choice, +# e.g. from $PCP_PMCDCONF_PATH +# this will be set on return if we've +# found an acceptable config file +# +# default_configfile +# "" if set, this is the default which +# will be offered +# +# Note: +# If the choice is aborted then $configfile will be set to empty. +# Therefore, there should be a test for an empty $configfile after +# the call to this function. +# +_choose_configfile() +{ + configdir=${configdir-$PCP_VAR_DIR/config/$iam} + + if [ ! -d $configdir ] + then + mkdir -p $configdir + fi + + while true + do + echo "Possible configuration files to choose from:" + # List viable alternatives + __i=0 # menu item number + __filelist="" # list of configuration files + __choice="" # the choice of configuration file + __choice1="" # the menu item for the 1st possible choice + __choice2="" # the menu item for the 2nd possible choice + __choice3="" # the menu item for the 3rd possible choice + + if [ ! -z "$configfile" ] + then + if [ -f $configfile ] + then + __i=`expr $__i + 1` + __choice1=$__i + __filelist="$__filelist $configfile" + echo "[$__i] $configfile" + fi + fi + + if [ -f $configdir/$iam.conf ] + then + if echo $__filelist | grep "$configdir/$iam.conf" >/dev/null + then + : + else + __i=`expr $__i + 1` + __choice2=$__i + __filelist="$__filelist $configdir/$iam.conf" + echo "[$__i] $configdir/$iam.conf" + fi + fi + + if [ -f $default_configfile ] + then + if echo $__filelist | grep "$default_configfile" >/dev/null + then + : + else + __i=`expr $__i + 1` + __choice3=$__i + __filelist="$__filelist $default_configfile" + echo "[$__i] $default_configfile" + fi + fi + + __i=`expr $__i + 1` + __own_choice=$__i + echo "[$__i] Specify your own configuration file." + + __i=`expr $__i + 1` + __abort_choice=$__i + echo "[$__i] None of the above (abandon configuration file selection)." + + $PCP_ECHO_PROG $PCP_ECHO_N "Which configuration file do you want to use ? [1] ""$PCP_ECHO_C" + read __reply + $__echo && echo "$__reply" + + # default + if [ -z "$__reply" ] + then + __reply=1 + fi + + # Process the reply from the user + if [ $__reply = $__own_choice ] + then + $PCP_ECHO_PROG $PCP_ECHO_N "Enter the name of the existing configuration file: ""$PCP_ECHO_C" + read __choice + $__echo && echo "$__choice" + if [ ! -f "$__choice" ] + then + echo "Cannot open \"$__choice\"." + echo "" + echo "Please choose another configuration file." + __choice="" + fi + elif [ $__reply = $__abort_choice ] + then + echo "Abandoning configuration file selection." + configfile="" + return 0 + elif [ "X$__reply" = "X$__choice1" -o "X$__reply" = "X$__choice2" -o "X$__reply" = "X$__choice3" ] + then + # extract nth field as the file + __choice=`echo $__filelist | $PCP_AWK_PROG -v n=$__reply '{ print $n }'` + else + echo "Illegal choice: $__reply" + echo "" + echo "Please choose number between: 1 and $__i" + fi + + if [ ! -z "$__choice" ] + then + echo + echo "Contents of the selected configuration file:" + echo "--------------- start $__choice ---------------" + cat $__choice + echo "--------------- end $__choice ---------------" + echo + + $PCP_ECHO_PROG $PCP_ECHO_N "Use this configuration file? [y] ""$PCP_ECHO_C" + read ans + $__echo && echo "$ans" + if [ ! -z "$ans" -a "X$ans" != Xy -a "X$ans" != XY ] + then + echo "" + echo "Please choose another configuration file." + else + break + fi + fi + done + + + __dest=$configdir/$iam.conf + if [ "$__choice" != "$__dest" ] + then + if [ -f $__dest ] + then + echo "Removing old configuration file \"$__dest\"" + rm -f $__dest + if [ -f $__dest ] + then + echo "Error: cannot remove old configuration file \"$__dest\"" + exit 1 + fi + fi + if cp $__choice $__dest + then + : + else + echo "Error: cannot install new configuration file \"$__dest\"" + exit 1 + fi + __choice=$__dest + fi + + configfile=$__choice +} + +# choose correct PMDA installation mode +# +# make sure we are installing in the correct style of configuration +# +__choose_mode() +{ + __def=m + $do_pmda && __def=b + echo \ +'You will need to choose an appropriate configuration for installation of +the "'$iam'" Performance Metrics Domain Agent (PMDA). + + collector collect performance statistics on this system + monitor allow this system to monitor local and/or remote systems + both collector and monitor configuration for this system +' + while true + do + $PCP_ECHO_PROG $PCP_ECHO_N 'Please enter c(ollector) or m(onitor) or b(oth) ['$__def'] '"$PCP_ECHO_C" + read ans + $__echo && echo "$ans" + case "$ans" + in + "") break + ;; + c|collector|b|both) + do_pmda=true + break + ;; + m|monitor) + do_pmda=false + break + ;; + *) echo "Sorry, that is not acceptable response ..." + ;; + esac + done +} + +# choose an IPC method +# +__choose_ipc() +{ + _dir=$1 + ipc_type='' + $pipe_opt && ipc_type=pipe + $socket_opt && ipc_type=socket + $pipe_opt && $socket_opt && ipc_type='' + if [ -z "$ipc_type" ] + then + while true + do + $PCP_ECHO_PROG $PCP_ECHO_N "PMCD should communicate with the $iam daemon via a pipe or a socket? [pipe] ""$PCP_ECHO_C" + read ipc_type + $__echo && echo "$ipc_type" + if [ "X$ipc_type" = Xpipe -o "X$ipc_type" = X ] + then + ipc_type=pipe + break + elif [ "X$ipc_type" = Xsocket ] + then + break + else + echo "Must choose one of \"pipe\" or \"socket\", please try again" + fi + done + fi + + if [ $ipc_type = pipe ] + then + # This defaults to binary unless the Install file + # specifies ipc_prot="binary notready" -- See pmcd(1) + type="pipe $ipc_prot $_dir/$pmda_name" + else + while true + do + $PCP_ECHO_PROG $PCP_ECHO_N "Use Internet, IPv6 or Unix domain sockets? [Internet] ""$PCP_ECHO_C" + read ans + $__echo && echo "$ans" + if [ "X$ans" = XInternet -o "X$ans" = XIPv6 -o "X$ans" = X ] + then + $PCP_ECHO_PROG $PCP_ECHO_N "Internet port number or service name? [$socket_inet_def] ""$PCP_ECHO_C" + read port + $__echo && echo "$port" + [ "X$port" = X ] && port=$socket_inet_def + case $port + in + [0-9]*) + ;; + *) + if grep "^$port[ ]*[0-9]*/tcp" /etc/services >/dev/null 2>&1 + then + : + else + echo "Warning: there is no tcp service for \"$port\" in /etc/services!" + fi + ;; + esac + if [ "X$ans" = XInternet -o "X$ans" = X ] + then + type="socket inet $port $_dir/$pmda_name" + args="-i $port $args" + else + type="socket ipv6 $port $_dir/$pmda_name" + args="-6 $port $args" + fi + break + elif [ "X$ans" = XUnix ] + then + $PCP_ECHO_PROG $PCP_ECHO_N "Unix FIFO name? ""$PCP_ECHO_C" + read fifo + $__echo && echo "$fifo" + if [ "X$fifo" = X ] + then + echo "Must provide a name, please try again" + else + type="socket unix $fifo $_dir/$pmda_name" + args="-u $fifo $args" + break + fi + else + echo "Must choose one of \"Unix\" or \"Internet\", please try again" + fi + done + fi +} + +# filter pmprobe -i output of the format: +# postgresql.active.is_in_recovery -12351 Missing metric value(s) +# postgresql.statio.sys_sequences.blks_hit 0 +# disk.partitions.read 13 ?0 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9 ?10 ?11 ?12 +# to produce a summary of metrics, values and warnings +# +__filter() +{ + $PCP_AWK_PROG ' +/^'$1'/ { metric++ + if ($2 < 0) + warn++ + else + value += $2 + next + } + { warn++ } +END { if (warn) printf "%d warnings, ",warn + printf "%d metrics and %d values\n",metric,value + }' +} + +_setup() +{ + # some more configuration controls + pmns_name=${pmns_name-$iam} + pmda_name=pmda$iam + pmda_dso_name="${PCP_PMDAS_DIR}/${iam}/pmda_${iam}.${dso_suffix}" + dso_name="${dso_name-$pmda_dso_name}" + dso_entry=${iam}_init + + _check_userroot + _check_directory + + # automatically generate files for those lazy Perl programmers + # + if $perl_opt + then + perl_name="${pmda_dir}/pmda${iam}.perl" + [ -f "$perl_name" ] || perl_name="${pmda_dir}/pmda${iam}.pl" + if [ -f "$perl_name" ] + then + perl_pmns="${pmda_dir}/pmns.perl" + perl_dom="${pmda_dir}/domain.h.perl" + perl -e 'use PCP::PMDA' 2>/dev/null + if test $? -eq 0 + then + eval PCP_PERL_DOMAIN=1 perl "$perl_name" > "$perl_dom" + eval PCP_PERL_PMNS=1 perl "$perl_name" > "$perl_pmns" + elif $dso_opt || $daemon_opt + then + : # we have an alternative, so continue on + else + echo 'Perl PCP::PMDA module is not installed, install it and try again' + exit 1 + fi + else + if $dso_opt || $daemon_opt + then + : # we have an alternative, so continue on + else + echo "Neither pmda${iam}.perl nor pmda${iam}.pl found in ${pmda_dir}" + echo "Error: no Perl PMDA to install" + exit 1 + fi + fi + fi + + # automatically generate files for the Python programmers too + # + if $python_opt + then + python_name="${pmda_dir}/pmda${iam}.python" + [ -f "$python_name" ] || python_name="${pmda_dir}/pmda${iam}.py" + if [ -f "$python_name" ] + then + python_pmns="${pmda_dir}/pmns.python" + python_dom="${pmda_dir}/domain.h.python" + python -c 'from pcp import pmda' 2>/dev/null + if test $? -eq 0 + then + eval PCP_PYTHON_DOMAIN=1 python "$python_name" > "$python_dom" + eval PCP_PYTHON_PMNS=1 python "$python_name" > "$python_pmns" + elif $dso_opt || $daemon_opt + then + : # we have an alternative, so continue on + else + echo 'Python pcp.pmda module is not installed, install it and try again' + exit 1 + fi + else + if $dso_opt || $daemon_opt + then + : # we have an alternative, so continue on + else + echo "Neither pmda${iam}.python nor pmda${iam}.py found in ${pmda_dir}" + echo "Error: no Python PMDA to install" + exit 1 + fi + fi + fi + + # Juggle pmns and domain.h in case perl/python pmda install was done here + # last time + # + for file in pmns domain.h + do + [ -f $file.save ] && mv $file.save $file + done + + # Set $domain and $SYMDOM from domain.h + # + __check_domain + + case $prog + in + *Install*) + # Check that $ROOT is not set, we have a default domain value and + # choose the installation mode (collector, monitor or both) + # + __check_root + __choose_mode + ;; + esac +} + +_install_views() +{ + viewer="$1" + have_views=false + + [ `echo *.$viewer` != "*.$viewer" ] && have_views=true + if [ -d $PCP_VAR_DIR/config/$viewer ] + then + $have_views && echo "Installing $viewer view(s) ..." + for __i in *.$viewer + do + if [ "$__i" != "*.$viewer" ] + then + __dest=$PCP_VAR_DIR/config/$viewer/`basename $__i .$viewer` + rm -f $__dest + cp $__i $__dest + fi + done + else + $have_views && \ + echo "Skip installing $viewer view(s) ... no \"$PCP_VAR_DIR/config/$viewer\" directory" + fi +} + +# Configurable PMDA installation +# +# before calling _install, +# 1. set $iam +# 2. set one/some/all of $dso_opt, $perl_opt, $python_opt or $daemon_opt to true +# (optional, $daemon_opt is true by default) +# 3. if $daemon_opt set one or both of $pipe_opt or $socket_opt true +# (optional, $pipe_opt is true by default) +# 4. if $socket_opt and there is a default Internet socket, set +# $socket_inet_def + +_install() +{ + if [ -z "$iam" ] + then + echo 'Botch: must define $iam before calling _install()' + exit 1 + fi + + if $do_pmda + then + if $dso_opt || $perl_opt || $python_opt || $daemon_opt + then + : + else + echo 'Botch: must set at least one of $dso_opt, $perl_opt, $python_opt or $daemon_opt to "true"' + exit 1 + fi + if $daemon_opt + then + if $pipe_opt || $socket_opt + then + : + else + echo 'Botch: must set at least one of $pipe_opt or $socket_opt to "true"' + exit 1 + fi + fi + + # Select a PMDA style (dso/perl/python/deamon), and for daemons the + # IPC method for communication between PMCD and the PMDA. + # + pmda_options='' + pmda_default_option='' + pmda_multiple_options=false + + if $dso_opt + then + pmda_options="dso" + pmda_default_option="dso" + fi + if $perl_opt + then + pmda_default_option="perl" + if test -n "$pmda_options" + then + pmda_options="perl or $pmda_options" + pmda_multiple_options=true + else + pmda_options="perl" + fi + fi + if $python_opt + then + pmda_default_option="python" + if test -n "$pmda_options" + then + pmda_options="python or $pmda_options" + pmda_multiple_options=true + else + pmda_options="python" + fi + fi + if $daemon_opt + then + pmda_default_option="daemon" + if test -n "$pmda_options" + then + pmda_options="daemon or $pmda_options" + pmda_multiple_options=true + else + pmda_options="daemon" + fi + fi + + pmda_type="$pmda_default_option" + if $pmda_multiple_options + then + while true + do + $PCP_ECHO_PROG $PCP_ECHO_N "Install $iam as a $pmda_options agent? [$pmda_default_option] ""$PCP_ECHO_C" + read pmda_type + $__echo && echo "$pmda_type" + if [ "X$pmda_type" = Xdaemon -o "X$pmda_type" = X ] + then + pmda_type=daemon + break + elif [ "X$pmda_type" = Xdso ] + then + break + elif [ "X$pmda_type" = Xperl ] + then + perl -e 'use PCP::PMDA' 2>/dev/null + if test $? -ne 0 + then + echo 'Perl PCP::PMDA module is not installed, install it and try again' + else + break + fi + elif [ "X$pmda_type" = Xpython ] + then + python -c 'from pcp import pmda' 2>/dev/null + if test $? -ne 0 + then + echo 'Python pcp pmda module is not installed, install it and try again' + else + break + fi + else + echo "Must choose one of $pmda_options, please try again" + fi + done + fi + if [ "$pmda_type" = daemon ] + then + __choose_ipc $pmda_dir + args="-d $domain $args" + elif [ "$pmda_type" = perl ] + then + type="pipe binary perl $perl_name $perl_args" + args="" + elif [ "$pmda_type" = python ] + then + type="pipe binary python $python_name $python_args" + args="" + else + type="dso $dso_entry $dso_name" + args="" + fi + + # Install binaries + # + if [ "$pmda_type" = perl -o "$pmda_type" = python ] + then + : # we can safely skip building binaries + elif [ -f Makefile -o -f makefile -o -f GNUmakefile ] + then + # $PCP_MAKE_PROG may contain command line args ... executable + # is first word + # + if [ ! -f "`echo $PCP_MAKE_PROG | sed -e 's/ .*//'`" -o ! -f "$PCP_INC_DIR/pmda.h" ] + then + echo "$prog: Arrgh, PCP devel environment required to install this PMDA" + exit 1 + fi + + echo "Installing files ..." + if $PCP_MAKE_PROG install + then + : + else + echo "$prog: Arrgh, \"$PCP_MAKE_PROG install\" failed!" + exit 1 + fi + fi + + # Fix domain in help for instance domains (if any) + # + if [ -f $help_source ] + then + case $pmda_interface + in + 1) + help_version=1 + ;; + *) # PMDA_INTERFACE_2 or later + help_version=2 + ;; + esac + sed -e "/^@ $SYMDOM\./s/$SYMDOM\./$domain./" <$help_source \ + | newhelp -n root -v $help_version -o $help_source + fi + fi + + if $do_pmda + then + if [ "X$pmda_type" = Xperl -o "X$pmda_type" = Xpython ] + then + # Juggle pmns and domain.h ... save originals and + # use *.{perl,python} ones created earlier + for file in pmns domain.h + do + if [ ! -f "$file.$pmda_type" ] + then + echo "Botch: $file.$pmda_type missing ... giving up" + exit 1 + fi + if [ -f $file ] + then + if diff $file.$pmda_type $file >/dev/null + then + : + else + [ ! -f $file.save ] && mv $file $file.save + mv $file.$pmda_type $file + fi + else + mv $file.$pmda_type $file + fi + done + fi + else + # Maybe PMNS only install, and only implementation may be + # Perl or Python ones ... simpler juggling needed here. + # + for file in pmns domain.h + do + [ ! -f $file -a -f $file.perl ] && mv $file.perl $file + [ ! -f $file -a -f $file.python ] && mv $file.python $file + done + fi + + $PCP_SHARE_DIR/lib/lockpmns $NAMESPACE + trap "$PCP_SHARE_DIR/lib/unlockpmns \$NAMESPACE; rm -rf $pcptmp $pmdatmp; exit" 0 1 2 3 15 + + echo "Updating the Performance Metrics Name Space (PMNS) ..." + + # Install the namespace + # + + if [ ! -f $NAMESPACE ] + then + # We may be installing an agent right after an install - + # before pmcd startup, which has a pre-execution step of + # rebuilding the namespace root. Do so now. + if [ -x $PMNSDIR/Rebuild ] + then + echo "$prog: cannot Rebuild the PMNS for \"$NAMESPACE\"" + exit 1 + fi + cd $PMNSDIR + ./Rebuild -dus + cd $__here + forced_restart=true + fi + + for __n in $pmns_name + do + if pminfo $__ns_opt $__n >/dev/null 2>&1 + then + cd $PMNSDIR + if pmnsdel -n $PMNSROOT $__n >$tmp/base 2>&1 + then + pmsignal -a -s HUP pmcd >/dev/null 2>&1 + # Make sure the PMNS timestamp will be different the next + # time the PMNS is updated (for Linux only 1 sec resolution) + sleep 2 + else + if grep 'Non-terminal "'"$__n"'" not found' $tmp/base >/dev/null + then + : + elif grep 'Error: metricpath "'"$__n"'" not defined' $tmp/base >/dev/null + then + : + else + echo "$prog: failed to delete \"$__n\" from the PMNS" + cat $tmp/base + exit 1 + fi + fi + cd $__here + fi + + # Put the default domain number into the namespace file + # + # If there is only one namespace, then the pmns file will + # be named "pmns". If there are multiple metric trees, + # subsequent pmns files will be named "pmns.<metricname>" + # + # the string "pmns" can be overridden by the Install/Remove + # scripts by altering $pmns_source + # + if [ "$__n" = "$iam" -o "$__n" = "$pmns_name" ] + then + __s=$pmns_source + else + __s=$pmns_source.$__n + fi + sed -e "s/$SYMDOM:/$domain:/" <$__s >$PMNSDIR/$__n + + cd $PMNSDIR + if pmnsadd -n $PMNSROOT $__n + then + pmsignal -a -s HUP pmcd >/dev/null 2>&1 + # Make sure the PMNS timestamp will be different the next + # time the PMNS is updated (for Linux only 1 sec resolution) + sleep 2 + else + echo "$prog: failed to add the PMNS entries for \"$__n\" ..." + echo + ls -l + exit 1 + fi + cd $__here + done + + trap "rm -rf $pcptmp $pmdatmp; exit" 0 1 2 3 15 + $PCP_SHARE_DIR/lib/unlockpmns $NAMESPACE + + _install_views pmchart + _install_views kmchart + _install_views pmview + + if $do_pmda + then + # Terminate old PMDA + # + echo "Terminate PMDA if already installed ..." + __pmda_cull $iam $domain + + # Rotate log files + # + if [ -f $PCP_LOG_DIR/pmcd/$iam.log ] + then + rm -f $PCP_LOG_DIR/pmcd/$iam.log.prev + mv -f $PCP_LOG_DIR/pmcd/$iam.log $PCP_LOG_DIR/pmcd/$iam.log.prev + fi + + # Add PMDA to pmcd's configuration file + # + echo "Updating the PMCD control file, and notifying PMCD ..." + __pmda_add "$iam $domain $type $args" + + # Check that the agent is running OK + # + if $do_check + then + [ "$check_delay" -gt 5 ] && echo "Wait $check_delay seconds for the $iam agent to initialize ..." + sleep $check_delay + for __n in $pmns_name + do + $PCP_ECHO_PROG $PCP_ECHO_N "Check $__n metrics have appeared ... ""$PCP_ECHO_C" + pmprobe -i $__ns_opt $__n | tee $tmp/verbose | __filter $__n + if $__verbose + then + echo "pminfo output ..." + cat $tmp/verbose + fi + done + fi + else + echo "Skipping PMDA install and PMCD re-configuration" + fi +} + +_remove() +{ + # Update the namespace + # + + $PCP_SHARE_DIR/lib/lockpmns $NAMESPACE + trap "$PCP_SHARE_DIR/lib/unlockpmns \$NAMESPACE; rm -rf $pcptmp $pmdatmp; exit" 0 1 2 3 15 + + echo "Culling the Performance Metrics Name Space ..." + cd $PMNSDIR + + for __n in $pmns_name + do + $PCP_ECHO_PROG $PCP_ECHO_N "$__n ... ""$PCP_ECHO_C" + if pmnsdel -n $PMNSROOT $__n >$tmp/base 2>&1 + then + rm -f $PMNSDIR/$__n + pmsignal -a -s HUP pmcd >/dev/null 2>&1 + sleep 2 + echo "done" + else + if grep 'Non-terminal "'"$__n"'" not found' $tmp/base >/dev/null + then + echo "not found in Name Space, this is OK" + elif grep 'Error: metricpath "'"$__n"'" not defined' $tmp/base >/dev/null + then + echo "not found in Name Space, this is OK" + else + echo "error" + cat $tmp/base + exit + fi + fi + done + + # Remove the PMDA and help files + # + cd $__here + + if $do_pmda + then + echo "Updating the PMCD control file, and notifying PMCD ..." + __pmda_cull $iam $domain + + if [ -f Makefile -o -f makefile -o -f GNUmakefile ] + then + echo "Removing files ..." + $PCP_MAKE_PROG clobber >/dev/null + fi + for __i in *.pmchart + do + if [ "$__i" != "*.pmchart" ] + then + __dest=$PCP_VAR_DIR/config/pmchart/`basename $__i .pmchart` + rm -f $__dest + fi + done + for __i in *.kmchart + do + if [ "$__i" != "*.kmchart" ] + then + __dest=$PCP_VAR_DIR/config/kmchart/`basename $__i .kmchart` + rm -f $__dest + fi + done + + if $do_check + then + for __n in $pmns_name + do + $PCP_ECHO_PROG $PCP_ECHO_N "Check $__n metrics have gone away ... ""$PCP_ECHO_C" + if pminfo -n $NAMESPACE -f $__n >$tmp/base 2>&1 + then + echo "Arrgh, something has gone wrong!" + cat $tmp/base + else + echo "OK" + fi + done + fi + else + echo "Skipping PMDA removal and PMCD re-configuration" + fi + + trap "rm -rf $pcptmp $pmdatmp; exit" 0 1 2 3 15 + $PCP_SHARE_DIR/lib/unlockpmns $NAMESPACE +} + +_check_userroot() +{ + if [ "$uid" -ne 0 ] + then + if [ -n "$PCP_DIR" ] + then + : running in a non-default installation, do not need to be root + else + echo "Error: You must be root (uid 0) to update the PCP collector configuration." + exit 1 + fi + fi +} + +_check_directory() +{ + case "$__here" + in + */pmdas/$iam) + ;; + *) + echo "Error: expect current directory to be .../pmdas/$iam, not $__here" + echo " (typical location is $PCP_PMDAS_DIR/$iam on this platform)" + exit 1 + ;; + esac +} + +# preferred public interfaces +# +pmdaSetup() +{ + _setup +} + +pmdaChooseConfigFile() +{ + _choose_configfile +} + +pmdaInstall() +{ + _install +} + +pmdaRemove() +{ + _remove +} diff --git a/src/pmcd/rc-proc.sh b/src/pmcd/rc-proc.sh new file mode 100644 index 0000000..3897c7f --- /dev/null +++ b/src/pmcd/rc-proc.sh @@ -0,0 +1,394 @@ +# +# Common sh(1) procedures to be used in PCP rc scripts +# +# Copyright (c) 2014 Red Hat. +# Copyright (c) 2000,2003 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. +# + +# source the PCP configuration environment variables +. $PCP_DIR/etc/pcp.env + +# These functions use chkconfig if available, else tolerate missing chkconfig +# command (as on SUSE) by manipulating symlinks in /etc/rc.d directly. +# +# Usage: +# +# is_chkconfig_on : return 0 if $1 is chkconfig "on" else 1 +# chkconfig_on : chkconfig $1 "on" +# chkconfig_off : chkconfig $1 "off" +# chkconfig_on_msg: echo a message about how to chkconfig $1 on +# + +# +# private functions +# +_which() +{ + # some versions of which(1) have historically not reflected the + # correct exit status ... but it appears that all modern platforms + # get this correct + # + # keeping the old logic structure, just in case + # + + if $PCP_WHICH_PROG $1 >/dev/null 2>&1 + then + if [ "$PCP_PLATFORM" = broken ] + then + if $PCP_WHICH_PROG $1 | grep "no $1" >/dev/null + then + : + else + return 0 + fi + else + return 0 + fi + fi + return 1 +} + +_cmds_exist() +{ + _have_flag=false + [ -f $PCP_RC_DIR/$1 ] && _have_flag=true + + _have_systemctl=false + _which systemctl && _have_systemctl=true + _have_runlevel=false + _which runlevel && _have_runlevel=true + _have_chkconfig=false + _which chkconfig && _have_chkconfig=true + _have_sysvrcconf=false + _which sysvrcconf && _have_sysvrcconf=true + _have_rcupdate=false + _which rc-update && _have_rcupdate=true + _have_svcadm=false + _which svcadm && _have_svcadm=true +} + +# +# return the run levels for $1 +# +_runlevels() +{ + $PCP_AWK_PROG '/^# chkconfig:/ {print $3}' $PCP_RC_DIR/$1 | sed -e 's/[0-9]/& /g' +} + +# +# return rc start number for $1 +# +_runlevel_start() +{ + $PCP_AWK_PROG '/^# chkconfig:/ {print $4}' $PCP_RC_DIR/$1 +} + +# +# return runlevel stop number for $1 +# +_runlevel_stop() +{ + $PCP_AWK_PROG '/^# chkconfig:/ {print $5}' $PCP_RC_DIR/$1 +} + +# +# Return 0 if $1 is chkconfig "on" (enabled) at the current run level +# Handles missing chkconfig command and other assorted atrocities. +# +is_chkconfig_on() +{ + # if non-default install, everything is "on" + [ -n "$PCP_DIR" ] && return 0 + + LANG=C + _flag=$1 + + _ret=1 # return "off" by default + _rl=3 # default run level if !_have_runlevel + + _cmds_exist $_flag + $_have_runlevel && _rl=`runlevel | $PCP_AWK_PROG '{print $2}'` + + if [ "$PCP_PLATFORM" = mingw -o "$PCP_PLATFORM" = "freebsd" ] + then + # unknown mechanism, just do it + _ret=0 + elif [ "$PCP_PLATFORM" = "darwin" ] + then + case "$1" + in + pmcd) [ "`. /etc/hostconfig; echo $PMCD`" = "-YES-" ] && _ret=0 ;; + pmlogger) [ "`. /etc/hostconfig; echo $PMLOGGER`" = "-YES-" ] && _ret=0 ;; + pmie) [ "`. /etc/hostconfig; echo $PMIE`" = "-YES-" ] && _ret=0 ;; + pmproxy) [ "`. /etc/hostconfig; echo $PMPROXY`" = "-YES-" ] && _ret=0 ;; + pmwebd) [ "`. /etc/hostconfig; echo $PMWEBD`" = "-YES-" ] && _ret=0 ;; + esac + elif $_have_systemctl + then + systemctl is-enabled "$_flag".service >/dev/null 2>&1 && _ret=0 + elif $_have_chkconfig + then + chkconfig --list "$_flag" 2>&1 | grep $_rl":on" >/dev/null 2>&1 && _ret=0 + elif $_have_sysvrcconf + then + sysv-rc-conf --list "$_flag" 2>&1 | grep $_rl":on" >/dev/null 2>&1 && _ret=0 + elif $_have_rcupdate + then + rc-update show 2>&1 | grep "$_flag" >/dev/null 2>&1 && _ret=0 + elif $_have_svcadm + then + svcs -l pcp/$_flag | grep "enabled *true" >/dev/null 2>&1 && _ret=0 + else + # + # don't know, fallback to using the existence of rc symlinks + # + if [ -f /etc/debian_version ]; then + ls /etc/rc$_rl.d/S[0-9]*$_flag >/dev/null 2>&1 && _ret=0 + else + ls /etc/rc.d/rc$_rl.d/S[0-9]*$_flag >/dev/null 2>&1 && _ret=0 + fi + fi + + return $_ret +} + +# +# chkconfig "on" $1 +# Handles missing chkconfig command. +# +chkconfig_on() +{ + # if non-default install, everything is "on" + [ -n "$PCP_DIR" ] && return 0 + + _flag=$1 + [ -z "$_flag" ] && return 1 # fail + + _cmds_exist $_flag + $_have_flag || return 1 # fail + + if [ "$PCP_PLATFORM" = mingw -o "$PCP_PLATFORM" = "freebsd" ] + then + # unknown mechanism, just pretend + return 0 + elif [ "$PCP_PLATFORM" = "darwin" ] + then + echo "To enable $_flag, add the following line to /etc/hostconfig:" + case "$_flag" + in + pmcd) echo "PMCD=-YES-" ;; + pmlogger) echo "PMLOGGER=-YES-" ;; + pmie) echo "PMIE=-YES-" ;; + pmproxy) echo "PMPROXY=-YES-" ;; + pmwebd) echo "PMWEBD=-YES-" ;; + esac + elif $_have_systemctl + then + systemctl --no-reload enable "$_flag".service >/dev/null 2>&1 + elif $_have_chkconfig + then + chkconfig "$_flag" on >/dev/null 2>&1 + elif $_have_sysvrcconf + then + sysv-rc-conf "$_flag" on >/dev/null 2>&1 + elif $_have_rcupdate + then + rc-update add "$_flag" >/dev/null 2>&1 + elif $_have_svcadm + then + svcadm enable pcp/$_flag >/dev/null 2>&1 + else + _start=`_runlevel_start $_flag` + _stop=`_runlevel_stop $_flag` + if [ -f /etc/debian_version ] + then + update-rc.d -f $_flag defaults s$_start k$_stop + else + for _r in `_runlevels $_flag` + do + ln -sf ../init.d/$_flag /etc/rc.d/rc$_r.d/S$_start""$_flag >/dev/null 2>&1 + ln -sf ../init.d/$_flag /etc/rc.d/rc$_r.d/K$_stop""$_flag >/dev/null 2>&1 + done + fi + fi + + return 0 +} + +# +# chkconfig "off" $1 +# Handles missing chkconfig command. +# +chkconfig_off() +{ + # if non-default install, everything is "on" + [ -n "$PCP_DIR" ] && return 1 + + _flag=$1 + [ -z "$_flag" ] && return 1 # fail + + _cmds_exist $_flag + $_have_flag || return 1 # fail + + if [ "$PCP_PLATFORM" = mingw -o "$PCP_PLATFORM" = "freebsd" ] + then + # unknown mechanism, just pretend + return 0 + elif $_have_systemctl + then + systemctl --no-reload disable "$_flag".service >/dev/null 2>&1 + elif $_have_chkconfig + then + chkconfig --level 2345 "$_flag" off >/dev/null 2>&1 + elif $_have_sysvrcconf + then + sysv-rc-conf --level 2345 "$_flag" off >/dev/null 2>&1 + elif $_have_rcupdate + then + rc-update delete "$_flag" >/dev/null 2>&1 + elif $_have_svcadm + then + svcadm disable pcp/$_flag >/dev/null 2>&1 + else + # remove the symlinks + if [ -f /etc/debian_version ] + then + update-rc.d -f $_flag remove + else + rm -f /etc/rc.d/rc[0-9].d/[SK][0-9]*$_flag >/dev/null 2>&1 + fi + fi + + return 0 +} + +# +# Echo a message about how to chkconfig $1 "on" +# Tolerates missing chkconfig command +# +chkconfig_on_msg() +{ + _flag=$1 + _cmds_exist $_flag + $_have_flag || return 1 # fail + + if [ "$PCP_PLATFORM" = mingw -o "$PCP_PLATFORM" = "freebsd" ] + then + # no mechanism, just pretend + # + return 0 + else + echo " To enable $_flag, run the following as root:" + if $_have_systemctl + then + _cmd=`$PCP_WHICH_PROG systemctl` + echo " # $_cmd enable $_flag.service" + elif $_have_chkconfig + then + _cmd=`$PCP_WHICH_PROG chkconfig` + echo " # $_cmd $_flag on" + elif $_have_sysvrcconf + then + _cmd=`$PCP_WHICH_PROG sysvrcconf` + echo " # $_cmd $_flag on" + elif $_have_rcupdate + then + _cmd=`$PCP_WHICH_PROG rc-update` + echo " # $_cmd add $_flag" + elif $_have_svcadm + then + _cmd=`$PCP_WHICH_PROG svcadm` + echo " # $_cmd enable pcp/$_flag" + else + _start=`_runlevel_start $_flag` + _stop=`_runlevel_stop $_flag` + if [ -f /etc/debian_version ] + then + echo " update-rc.d -f $_flag remove" + echo " update-rc.d $_flag defaults $_start $_stop" + else + for _r in `_runlevels $_flag` + do + echo " # ln -sf ../init.d/$_flag /etc/rc.d/rc$_r.d/S$_start""$_flag" + echo " # ln -sf ../init.d/$_flag /etc/rc.d/rc$_r.d/K$_stop""$_flag" + done + fi + fi + fi + + return 0 +} + +# +# load some rc functions if available +# +# In openSUSE 12.1, /etc/rc.status intercepts our rc script and passes +# control to systemctl which uses systemd ... the result is that messages +# from our rc scripts are sent to syslog by default, and there is no +# apparent way to revert to the classical behaviour, so this "hack" allows +# PCP QA to set $PCPQA_NO_RC_STATUS and continue to see stdout and stderr +# from our rc scripts +# - Ken 1 Dec 2011 +# +if [ -r /etc/rc.status -a -z "${PCPQA_NO_RC_STATUS+set}" ] +then + # SuSE style + . /etc/rc.status + RC_STATUS=rc_status + RC_RESET=rc_reset + RC_CHECKPROC=checkproc +else + # Roll our own + RC_STATUS=_RC_STATUS + _RC_STATUS() + { + _rc_status=$? + if [ "$1" = "-v" ] + then + if [ $_rc_status -eq 0 ] + then $ECHO + else + $ECHO "failed (status=$_rc_status)" + fi + fi + return $_rc_status + } + + RC_RESET=_RC_RESET + _RC_RESET() + { + _rc_status=0 + return $_rc_status + } + + RC_CHECKPROC=_RC_CHECKPROC + _RC_CHECKPROC() + { + # usage + [ $# -ne 1 ] && return 2 + + # running + _b=`basename "$1"` + _n=`_get_pids_by_name $_b | wc -l` + [ $_n -ge 1 ] && return 0 + + # not running, but pid exists + [ -e /var/run/$_b.pid ] && return 1 + + # program not installed + [ ! -e "$1" ] && return 5 + + # not running and no pid + return 3 + } +fi diff --git a/src/pmcd/rc-proc.sh.minimal b/src/pmcd/rc-proc.sh.minimal new file mode 100644 index 0000000..42e5b80 --- /dev/null +++ b/src/pmcd/rc-proc.sh.minimal @@ -0,0 +1,74 @@ +# +# Common sh(1) procedures to be used in PCP rc scripts +# +# Minimalist version - use this if your system's init script regime +# does not follow the "chkconfig + runlevel" model. +# +# Copyright (c) 2000,2003 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., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# + +# source the PCP configuration environment variables +. $PCP_DIR/etc/pcp.env + +# These functions use chkconfig if available, else tolerate missing chkconfig +# command (as on SUSE) by manipulating symlinks in /etc/rc.d directly. +# +# Usage: +# +# is_chkconfig_on : return 0 if $1 is chkconfig "on" else 1 +# chkconfig_on : chkconfig $1 "on" +# chkconfig_off : chkconfig $1 "off" +# chkconfig_on_msg: echo a message about how to chkconfig $1 on +# + +# +# Return 0 if $1 is chkconfig "on" (enabled) at the current run level +# Handles missing chkconfig command and other assorted atrocities. +# +is_chkconfig_on() +{ + return 0 +} + +# +# chkconfig "on" $1 +# Handles missing chkconfig command. +# (this is used by the pcp rpm %post script) +# +chkconfig_on() +{ + : +} + +# +# chkconfig "off" $1 +# Handles missing chkconfig command. +# (this is used by the pcp rpm %preun script) +# +chkconfig_off() +{ + : +} + +# +# Echo a message about how to chkconfig $1 "on" +# Tolerates missing chkconfig command +# +chkconfig_on_msg() +{ + : +} diff --git a/src/pmcd/rc_local b/src/pmcd/rc_local new file mode 100644 index 0000000..4e51942 --- /dev/null +++ b/src/pmcd/rc_local @@ -0,0 +1,67 @@ +#!/bin/sh +# +# Copyright (c) 2000,2004 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., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +# rc.local - perform local Performance Co-Pilot boot/shutdown/restart actions +# + +# Get standard environment +. $PCP_DIR/etc/pcp.env + +_usage() +{ + echo "Usage: $PCP_SYSCONF_DIR/pmcd/rc.local [-v] {start|stop}" +} + +# defaults +# +VERBOSE_CTL=off + +while getopts v c +do + case $c + in + v) # force verbose + VERBOSE_CTL=on + ;; + + *) + _usage + exit 1 + ;; + esac +done +shift `expr $OPTIND - 1` + +# uncomment this if you wish +# +# [ $VERBOSE_CTL = on ] && echo "Local Performance Co-Pilot $1 script called." + +case $1 in + + 'start') + # Add startup actions here + ;; + + 'stop') + # Add shutdown actions here + ;; + + *) + echo "Usage: $0 {start|stop}" + ;; +esac diff --git a/src/pmcd/rc_pcp b/src/pmcd/rc_pcp new file mode 100644 index 0000000..974ca06 --- /dev/null +++ b/src/pmcd/rc_pcp @@ -0,0 +1,76 @@ +#!/bin/sh +# +# Copyright (c) 2011 Ken McDonell. 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., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +# Legacy wrapper for the Performance Co-Pilot Daemon(s) Start / Stop scripts +# +# This script is NOT part of the "rc" framework ... the "pmcd" and "pmlogger" +# scripts ARE part of the "rc" framework. This script is provided as a +# legacy bridge for any scripts or procedures that used $PCP_RC_DIR/pcp +# from the days before PCP 3.6. +# +# The following is for chkconfig on RedHat based systems +# chkconfig: +# description: Legacy init script wrapper for the Performance Co-Pilot (PCP) daemons +# +# The following is for insserv(1) based systems, +# e.g. SuSE, where chkconfig is a perl script. +### BEGIN INIT INFO +# Provides: pcp +# Required-Start: +# Should-Start: +# Required-Stop: +# Should-Stop: +# Default-Start: +# Default-Stop: +# Short-Description: Legacy control for PCP daemons +# Description: Legacy init script wrapper for the Performance Co-Pilot (PCP) daemons +### END INIT INFO + +. $PCP_DIR/etc/pcp.env + +tmp=`mktemp -d /var/tmp/pcp.XXXXXXXXX` || exit 1 +status=0 +trap "rm -rf $tmp; exit \$status" 0 1 2 3 15 +prog=$PCP_RC_DIR/pcp + +_usage() +{ + echo "Usage: $pmprog [-v] {start|restart|condrestart|stop|status|reload|force-reload}" +} + +case "$1" in + + 'start'|'restart'|'condrestart'|'reload'|'force-reload') + $PCP_RC_DIR/pmcd $* + $PCP_RC_DIR/pmlogger $* + ;; + + 'stop') + $PCP_RC_DIR/pmlogger $* + $PCP_RC_DIR/pmcd $* + ;; + + 'status') + $PCP_RC_DIR/pmcd $* || status=$? + $PCP_RC_DIR/pmlogger $* || status=$? + ;; + + *) + _usage + ;; +esac diff --git a/src/pmcd/rc_pmcd b/src/pmcd/rc_pmcd new file mode 100644 index 0000000..49b7430 --- /dev/null +++ b/src/pmcd/rc_pmcd @@ -0,0 +1,540 @@ +#!/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 777 "$PCP_TMP_DIR/pmlogger" + chown $PCP_USER:$PCP_GROUP "$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 + + # 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 + $PCP_BINADM_DIR/pmpost "PMDA setup: automated install: $pmda" + rm -f .NeedInstall.sav + else + # put the file back, maybe we'll be luckier next time + $PCP_BINADM_DIR/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=$? + $PCP_BINADM_DIR/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 + $PCP_BINADM_DIR/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 + if [ ! -d "$RUNDIR" ] + then + mkdir -p -m 775 "$RUNDIR" + chown $PCP_USER:$PCP_GROUP "$RUNDIR" + fi + 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 + + $PCP_BINADM_DIR/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 + diff --git a/src/pmcd/sasl2.conf b/src/pmcd/sasl2.conf new file mode 100644 index 0000000..774149d --- /dev/null +++ b/src/pmcd/sasl2.conf @@ -0,0 +1,19 @@ +# Enabled authentication mechanisms (space-separated list). +# You can list many mechanisms at once, then the user can choose +# by adding e.g. '?authmech=gssapi' to their host specification. +# For other options, refer to SASL pluginviewer command output. +mech_list: plain login digest-md5 gssapi + +# If deferring to the SASL auth daemon (runs as root, can do PAM +# login using regular user accounts, unprivileged daemons cannot). +#pwcheck_method: saslauthd + +# If using plain/digest-md5 for user database, this sets the file +# containing the passwords. Use 'saslpasswd2 -a pmcd [username]' +# to add entries and 'sasldblistusers2 -f $sasldb_path' to browse. +# Note: must be readable as the PCP daemons user (chown root:pcp). +sasldb_path: /etc/pcp/passwd.db + +# Before using Kerberos via GSSAPI, you need a service principal on +# the KDC server for pmcd, and that to be exported to the keytab. +#keytab: /etc/pcp/krb5.tab diff --git a/src/pmcd/src/GNUmakefile b/src/pmcd/src/GNUmakefile new file mode 100644 index 0000000..86462c3 --- /dev/null +++ b/src/pmcd/src/GNUmakefile @@ -0,0 +1,38 @@ +# +# Copyright (c) 2012-2013 Red Hat. +# Copyright (c) 2000,2004 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. +# + +TOPDIR = ../../.. +include $(TOPDIR)/src/include/builddefs + +CMDTARGET = pmcd$(EXECSUFFIX) +HFILES = client.h pmcd.h +CFILES = pmcd.c config.c dofetch.c dopdus.c dostore.c client.c agent.c util.c + +LLDLIBS = $(PCPLIB) $(LIB_FOR_DLOPEN) -lpcp_pmcd +PCPLIB_LDFLAGS += -L$(TOPDIR)/src/libpcp_pmcd/$(LIBPCP_ABIDIR) + +LLDFLAGS += $(RDYNAMIC_FLAG) $(PIELDFLAGS) +LCFLAGS += $(PIECFLAGS) + +default: $(CMDTARGET) + +include $(BUILDRULES) + +install: default + $(INSTALL) -m 755 $(CMDTARGET) $(PCP_BINADM_DIR)/$(CMDTARGET) + +default_pcp: default + +install_pcp: install diff --git a/src/pmcd/src/agent.c b/src/pmcd/src/agent.c new file mode 100644 index 0000000..700a0f2 --- /dev/null +++ b/src/pmcd/src/agent.c @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2012-2013 Red Hat. + * Copyright (c) 1995-2005 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. + */ + +#include "pmcd.h" +#if defined(HAVE_DLFCN_H) +#include <dlfcn.h> +#elif defined(HAVE_DL_H) +#include <dl.h> +#endif +#if defined(HAVE_SYS_WAIT_H) +#include <sys/wait.h> +#endif +#if defined(HAVE_SYS_RESOURCE_H) +#include <sys/resource.h> +#endif + +/* Return a pointer to the agent that is reposible for a given domain. + * Note that the agent may not be in a connected state! + */ +AgentInfo * +FindDomainAgent(int domain) +{ + int i; + for (i = 0; i < nAgents; i++) + if (agent[i].pmDomainId == domain) + return &agent[i]; + return NULL; +} + +void +CleanupAgent(AgentInfo* aPtr, int why, int status) +{ + extern int AgentDied; +#ifndef IS_MINGW + int exit_status = status; +#endif + int reason = 0; + + if (aPtr->ipcType == AGENT_DSO) { + if (aPtr->ipc.dso.dlHandle != NULL) { +#ifdef HAVE_DLOPEN + dlclose(aPtr->ipc.dso.dlHandle); +#endif + } + pmcd_trace(TR_DEL_AGENT, aPtr->pmDomainId, -1, -1); + } + else { + pmcd_trace(TR_DEL_AGENT, aPtr->pmDomainId, aPtr->inFd, aPtr->outFd); + if (aPtr->inFd != -1) { + if (aPtr->ipcType == AGENT_SOCKET) + __pmCloseSocket(aPtr->inFd); + else { + close(aPtr->inFd); + __pmResetIPC(aPtr->inFd); + } + aPtr->inFd = -1; + } + if (aPtr->outFd != -1) { + if (aPtr->ipcType == AGENT_SOCKET) + __pmCloseSocket(aPtr->outFd); + else { + close(aPtr->outFd); + __pmResetIPC(aPtr->outFd); + } + aPtr->outFd = -1; + } + if (aPtr->ipcType == AGENT_SOCKET && + aPtr->ipc.socket.addrDomain == AF_UNIX) { + /* remove the Unix domain socket */ + unlink(aPtr->ipc.socket.name); + } + } + + __pmNotifyErr(LOG_INFO, "CleanupAgent ...\n"); + fprintf(stderr, "Cleanup \"%s\" agent (dom %d):", aPtr->pmDomainLabel, aPtr->pmDomainId); + + if (why == AT_EXIT) { + /* waitpid has already been done */ + fprintf(stderr, " terminated"); + reason = (status << 8) | REASON_EXIT; + } + else { + if (why == AT_CONFIG) { + fprintf(stderr, " unconfigured"); + } else { + reason = REASON_PROTOCOL; + fprintf(stderr, " protocol failure for fd=%d", status); +#ifndef IS_MINGW + exit_status = -1; +#endif + } + if (aPtr->status.isChild == 1) { + pid_t pid = (pid_t)-1; + pid_t done; + int wait_status; + int slept = 0; + + if (aPtr->ipcType == AGENT_PIPE) + pid = aPtr->ipc.pipe.agentPid; + else if (aPtr->ipcType == AGENT_SOCKET) + pid = aPtr->ipc.socket.agentPid; + for ( ; ; ) { + +#if defined(HAVE_WAIT3) + done = wait3(&wait_status, WNOHANG, NULL); +#elif defined(HAVE_WAITPID) + done = waitpid((pid_t)-1, &wait_status, WNOHANG); +#else + wait_status = 0; + done = 0; +#endif + if (done == pid) { +#ifndef IS_MINGW + exit_status = wait_status; +#endif + break; + } + if (done > 0) { + continue; + } + if (slept) { + break; + } + /* give PMDA a chance to notice the close() and exit */ + sleep(1); + slept = 1; + } + } + } +#ifndef IS_MINGW + if (exit_status != -1) { + if (WIFEXITED(exit_status)) { + fprintf(stderr, ", exit(%d)", WEXITSTATUS(exit_status)); + reason = (WEXITSTATUS(exit_status) << 8) | reason; + } + else if (WIFSIGNALED(exit_status)) { + fprintf(stderr, ", signal(%d)", WTERMSIG(exit_status)); +#ifdef WCOREDUMP + if (WCOREDUMP(exit_status)) + fprintf(stderr, ", dumped core"); +#endif + reason = (WTERMSIG(exit_status) << 16) | reason; + } + } +#endif + fputc('\n', stderr); + aPtr->reason = reason; + aPtr->status.connected = 0; + aPtr->status.busy = 0; + aPtr->status.notReady = 0; + aPtr->status.flags = 0; + AgentDied = 1; + + if (_pmcd_trace_mask) + pmcd_dump_trace(stderr); + + MarkStateChanges(PMCD_DROP_AGENT); +} + +/* Wait up to total secs for agents to terminate. + * Return 0 if all terminate, else -1 + */ +int +HarvestAgents(unsigned int total) +{ + int i; + int sts; + int found; + pid_t pid; + AgentInfo *ap; + + /* + * Check for child process termination. Be careful, and ignore any + * non-agent processes found. + */ + do { +#if defined(HAVE_WAIT3) + pid = wait3(&sts, WNOHANG, NULL); +#elif defined(HAVE_WAITPID) + pid = waitpid((pid_t)-1, &sts, WNOHANG); +#else + break; +#endif + found = 0; + for ( i = 0; i < nAgents; i++) { + ap = &agent[i]; + if (!ap->status.connected || ap->ipcType == AGENT_DSO) + continue; + + found = 1; + if (pid <= (pid_t)0) { + if (total--) { + sleep(1); + break; + } else { + return -1; + } + } + if (pid == ((ap->ipcType == AGENT_SOCKET) + ? ap->ipc.socket.agentPid + : ap->ipc.pipe.agentPid)) { + CleanupAgent(ap, AT_EXIT, sts); + break; + } + } + } while (found); + + return 0; +} diff --git a/src/pmcd/src/client.c b/src/pmcd/src/client.c new file mode 100644 index 0000000..9d46338 --- /dev/null +++ b/src/pmcd/src/client.c @@ -0,0 +1,265 @@ +/* + * Copyright (c) 2012-2013 Red Hat. + * Copyright (c) 1995-2001,2004 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. + */ + +#include "pmapi.h" +#include "impl.h" +#include "pmcd.h" + +#define MIN_CLIENTS_ALLOC 8 + +int maxClientFd = -1; /* largest fd for a client */ +__pmFdSet clientFds; /* for client select() */ + +static int clientSize; + +/* + * For PMDA_INTERFACE_5 or later PMDAs, post a notification that + * a context has been closed. + */ +static void +NotifyEndContext(int ctx) +{ + int i; + + for (i = 0; i < nAgents; i++) { + if (!agent[i].status.connected || + agent[i].status.busy || agent[i].status.notReady) + continue; + if (agent[i].ipcType == AGENT_DSO) { + pmdaInterface *dp = &agent[i].ipc.dso.dispatch; + if (dp->comm.pmda_interface >= PMDA_INTERFACE_5) { + if (dp->version.four.ext->e_endCallBack != NULL) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) { + fprintf(stderr, "NotifyEndContext: DSO PMDA %s (%d) notified of context %d close\n", + agent[i].pmDomainLabel, agent[i].pmDomainId, + ctx); + } +#endif + (*(dp->version.four.ext->e_endCallBack))(ctx); + } + } + } + else { + /* + * Daemon PMDA case ... we don't know the PMDA_INTERFACE + * version, so send the notification PDU anyway, and rely on + * __pmdaMainPDU() doing the right thing. + * Do not expect a response. + * Agent may have decided to spontaneously die so don't + * bother about any return status from the __pmSendError + * either. + */ +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) { + fprintf(stderr, "NotifyEndContext: daemon PMDA %s (%d) notified of context %d close\n", + agent[i].pmDomainLabel, agent[i].pmDomainId, ctx); + } +#endif + pmcd_trace(TR_XMIT_PDU, agent[i].inFd, PDU_ERROR, PM_ERR_NOTCONN); + __pmSendError(agent[i].inFd, ctx, PM_ERR_NOTCONN); + } + } +} + +/* Establish a new socket connection to a client */ +ClientInfo * +AcceptNewClient(int reqfd) +{ + static unsigned int seq = 0; + int i, fd; + __pmSockLen addrlen; + struct timeval now; + + i = NewClient(); + addrlen = __pmSockAddrSize(); + fd = __pmAccept(reqfd, client[i].addr, &addrlen); + if (fd == -1) { + if (neterror() == EPERM) { + __pmNotifyErr(LOG_NOTICE, "AcceptNewClient(%d): " + "Permission Denied\n", reqfd); + client[i].fd = -1; + DeleteClient(&client[i]); + return NULL; + } + else { + __pmNotifyErr(LOG_ERR, "AcceptNewClient(%d) __pmAccept: %s\n", + reqfd, netstrerror()); + Shutdown(); + exit(1); + } + } + if (fd > maxClientFd) + maxClientFd = fd; + + pmcd_openfds_sethi(fd); + + __pmFD_SET(fd, &clientFds); + __pmSetVersionIPC(fd, UNKNOWN_VERSION); /* before negotiation */ + __pmSetSocketIPC(fd); + + client[i].fd = fd; + client[i].status.connected = 1; + client[i].status.changes = 0; + memset(&client[i].attrs, 0, sizeof(__pmHashCtl)); + + /* + * Note seq needs to be unique, but we're using a free running counter + * and not bothering to check here ... unless we churn through + * 4,294,967,296 (2^32) clients while one client remains connected + * we won't have a problem + */ + client[i].seq = seq++; + __pmtimevalNow(&now); + client[i].start = now.tv_sec; + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) + fprintf(stderr, "AcceptNewClient(%d): client[%d] (fd %d)\n", reqfd, i, fd); +#endif + pmcd_trace(TR_ADD_CLIENT, i, 0, 0); + + return &client[i]; +} + +int +NewClient(void) +{ + int i, sz; + + for (i = 0; i < nClients; i++) + if (!client[i].status.connected) + break; + + if (i == clientSize) { + clientSize = clientSize ? clientSize * 2 : MIN_CLIENTS_ALLOC; + sz = sizeof(ClientInfo) * clientSize; + client = (ClientInfo *) realloc(client, sz); + if (client == NULL) { + __pmNoMem("NewClient", sz, PM_RECOV_ERR); + Shutdown(); + exit(1); + } + sz -= (sizeof(ClientInfo) * i); + memset(&client[i], 0, sz); + } + client[i].addr = __pmSockAddrAlloc(); + if (client[i].addr == NULL) { + __pmNoMem("NewClient", __pmSockAddrSize(), PM_RECOV_ERR); + Shutdown(); + exit(1); + } + if (i >= nClients) + nClients = i + 1; + return i; +} + +/* + * Expose ClientInfo struct for client #n + */ +ClientInfo * +GetClient(int n) +{ + if (0 <= n && n < nClients && client[n].status.connected) + return &client[n]; + return NULL; +} + +void +DeleteClient(ClientInfo *cp) +{ + int i; + + for (i = 0; i < nClients; i++) + if (cp == &client[i]) + break; + + if (i == nClients) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) { + __pmNotifyErr(LOG_ERR, "DeleteClient: tried to delete non-existent client\n"); + Shutdown(); + exit(1); + } +#endif + return; + } + if (cp->fd != -1) { + __pmFD_CLR(cp->fd, &clientFds); + __pmCloseSocket(cp->fd); + } + if (i == nClients-1) { + i--; + while (i >= 0 && !client[i].status.connected) + i--; + nClients = (i >= 0) ? i + 1 : 0; + } + if (cp->fd == maxClientFd) { + maxClientFd = -1; + for (i = 0; i < nClients; i++) { + if (client[i].fd > maxClientFd) + maxClientFd = client[i].fd; + } + } + for (i = 0; i < cp->szProfile; i++) { + if (cp->profile[i] != NULL) { + __pmFreeProfile(cp->profile[i]); + cp->profile[i] = NULL; + } + } + __pmFreeAttrsSpec(&cp->attrs); + __pmHashClear(&cp->attrs); + __pmSockAddrFree(cp->addr); + cp->addr = NULL; + cp->status.connected = 0; + cp->fd = -1; + + NotifyEndContext(cp-client); +} + +void +MarkStateChanges(int changes) +{ + int i; + + for (i = 0; i < nClients; i++) { + if (client[i].status.connected == 0) + continue; + client[i].status.changes |= changes; + } +} + +int +CheckAccountAccess(ClientInfo *cp) +{ + __pmHashNode *node; + const char *userid; + const char *groupid; + + userid = ((node = __pmHashSearch(PCP_ATTR_USERID, &cp->attrs)) ? + (const char *)node->data : NULL); + groupid = ((node = __pmHashSearch(PCP_ATTR_GROUPID, &cp->attrs)) ? + (const char *)node->data : NULL); + if (!userid || !groupid) + if (__pmServerHasFeature(PM_SERVER_FEATURE_CREDS_REQD)) + return PM_ERR_PERMISSION; + return __pmAccAddAccount(userid, groupid, &cp->denyOps); +} + +int +CheckClientAccess(ClientInfo *cp) +{ + return __pmAccAddClient(cp->addr, &cp->denyOps); +} diff --git a/src/pmcd/src/client.h b/src/pmcd/src/client.h new file mode 100644 index 0000000..2758e9f --- /dev/null +++ b/src/pmcd/src/client.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2012-2013 Red Hat. + * Copyright (c) 1995 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. + */ +#ifndef _CLIENT_H +#define _CLIENT_H + +/* The table of clients, used by pmcd */ +typedef struct { + int fd; /* Socket descriptor */ + struct { /* Status of connection to client */ + unsigned int connected : 1; /* Client connected */ + unsigned int changes : 3; /* PMCD_* bits for changes since last fetch */ + } status; + /* There is an array of profiles, as there is a profile associated + * with each client context. The array is not guaranteed to be dense. + * The context number sent with each profile/fetch is used to index it. + */ + __pmProfile **profile; /* Client context profile pointers */ + int szProfile; /* Size of array */ + unsigned int denyOps; /* Disallowed operations for client */ + __pmPDUInfo pduInfo; + unsigned int seq; /* Client sequence number (pmdapmcd) */ + time_t start; /* Time client connected (pmdapmcd) */ + __pmSockAddr *addr; /* Network address of client */ + __pmHashCtl attrs; /* Connection attributes (auth info) */ +} ClientInfo; + +PMCD_EXTERN ClientInfo *client; /* Array of clients */ +PMCD_EXTERN int nClients; /* Number of entries in array */ +extern int maxClientFd; /* largest fd for a client */ +extern __pmFdSet clientFds; /* for client select() */ +PMCD_EXTERN int this_client_id; /* client for current request */ + +/* prototypes */ +extern ClientInfo *AcceptNewClient(int); +extern int NewClient(void); +extern void DeleteClient(ClientInfo *); +extern ClientInfo *GetClient(int); +PMCD_EXTERN void ShowClients(FILE *m); +extern int CheckClientAccess(ClientInfo *); +extern int CheckAccountAccess(ClientInfo *); + +#ifdef PCP_DEBUG +extern char *nameclient(int); +#endif + +#endif /* _CLIENT_H */ diff --git a/src/pmcd/src/config.c b/src/pmcd/src/config.c new file mode 100644 index 0000000..740a0f5 --- /dev/null +++ b/src/pmcd/src/config.c @@ -0,0 +1,2526 @@ +/* + * Copyright (c) 2012-2013 Red Hat. + * Copyright (c) 1995-2005 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. + * + * PMCD routines for reading config file, creating children and + * attaching to DSOs. + */ + +#include "pmapi.h" +#include "impl.h" +#include "pmcd.h" +#include <ctype.h> +#include <sys/stat.h> +#if defined(HAVE_SYS_WAIT_H) +#include <sys/wait.h> +#endif +#if defined(HAVE_SYS_UN_H) +#include <sys/un.h> +#endif +#if defined(HAVE_DLFCN_H) +#include <dlfcn.h> +#elif defined(HAVE_DL_H) +#include <dl.h> +#endif + +#define MIN_AGENTS_ALLOC 3 /* Number to allocate first time */ +#define LINEBUF_SIZE 200 + +/* Config file modification time */ +#if defined(HAVE_STAT_TIMESTRUC) +static struct timestruc configFileTime; +#elif defined(HAVE_STAT_TIMESPEC) +static struct timespec configFileTime; +#elif defined(HAVE_STAT_TIMESPEC_T) +static timespec_t configFileTime; +#elif defined(HAVE_STAT_TIME_T) +static time_t configFileTime; +#else +!bozo! +#endif + +int szAgents; /* Number currently allocated */ +int mapdom[MAXDOMID+2]; /* The DomainId-to-AgentIndex map */ + /* Don't use it during parsing! */ + +static FILE *inputStream; /* Input stream for scanner */ +static int scanInit; +static int scanError; /* Problem in scanner */ +static char *linebuf; /* Buffer for input stream */ +static int szLineBuf; /* Allocated size of linebuf */ +static char *token; /* Start of current token */ +static char *tokenend; /* End of current token */ +static int nLines; /* Current line of config file */ +static int linesize; /* Length of line in linebuf */ + +/* Macro to compare a string with token. The linebuf is always null terminated + * so there are no nasty boundary conditions. + */ +#define TokenIs(str) ((tokenend - token) == strlen(str) && \ + !strncasecmp(token, str, strlen(str))) + +/* Return the numeric value of token (or zero if token is not numeric). */ +static int +TokenNumVal(void) +{ + int val = 0; + char *p = token; + while (isdigit((int)*p)) { + val = val * 10 + *p - '0'; + p++; + } + return val; +} + +/* Return true if token is a numeric value */ +static int +TokenIsNumber(void) +{ + char *p; + if (token == tokenend) /* Nasty end of input case */ + return 0; + for (p = token; isdigit((int)*p); p++) + ; + return p == tokenend; +} + +/* Return a strdup-ed copy of the current token. */ +static char* +CopyToken(void) +{ + int len = (int)(tokenend - token); + char *copy = (char *)malloc(len + 1); + if (copy != NULL) { + strncpy(copy, token, len); + copy[len] = '\0'; + } + return copy; +} + +/* Get the next line from the input stream into linebuf. */ + +static void +GetNextLine(void) +{ + char *end; + int more; /* There is more line to read */ + int still_to_read; + int atEOF = 0; + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL2) + fprintf(stderr, "%d: GetNextLine()\n", nLines); +#endif + + if (szLineBuf == 0) { + szLineBuf = LINEBUF_SIZE; + linebuf = (char *)malloc(szLineBuf); + if (linebuf == NULL) + __pmNoMem("pmcd config: GetNextLine init", szLineBuf, PM_FATAL_ERR); + } + + linebuf[0] = '\0'; + token = linebuf; + if (feof(inputStream)) + return; + + end = linebuf; + more = 0; + still_to_read = szLineBuf; + do { + /* Read into linebuf. If more is set, the read is into a realloc()ed + * version of linebuf. In this case, more is the number of characters + * at the end of the previous batch that should be overwritten + */ + if (fgets(end, still_to_read, inputStream) == NULL) { + if (!feof(inputStream)) { + fprintf(stderr, "pmcd config[line %d]: Error: fgets failed: %s\n", + nLines, osstrerror()); + scanError = 1; + return; + } + atEOF = 1; + } + + linesize = (int)strlen(linebuf); + more = 0; + if (linesize == 0) + break; + if (linebuf[linesize - 1] != '\n') { + if (feof(inputStream)) { + /* Final input line has no '\n', so add one. If a terminating + * null fits after it, that's the line, so break out of loop. + */ + linebuf[linesize] = '\n'; + /* Add terminating null if it fits */ + if (linesize + 1 < szLineBuf) { + linebuf[++linesize] = '\0'; + break; + } + /* If no room for null, get more buffer space */ + } + more = 1; /* More buffer space required */ + } + /* Check for continued lines */ + else if (linesize > 1 && linebuf[linesize - 2] == '\\') { + linebuf[linesize - 2] = ' '; + linesize--; /* Pretend the \n isn't there */ + more = 2; /* Overwrite \n and \0 on next read */ + } + + /* Make buffer larger to accomodate more of the line. */ + if (more) { + if (szLineBuf > 10 * LINEBUF_SIZE) { + fprintf(stderr, + "pmcd config[line %d]: Error: ridiculously long line (%d characters)\n", + nLines+1, szLineBuf); + linebuf[0] = '\0'; + scanError = 1; + return; + } + szLineBuf += LINEBUF_SIZE; + if ((linebuf = realloc(linebuf, szLineBuf)) == NULL) { + static char fallback[2]; + + __pmNoMem("pmcd config: GetNextLine", szLineBuf, PM_RECOV_ERR); + linebuf = fallback; + linebuf[0] = '\0'; + scanError = 1; + return; + } + end = linebuf + linesize; + still_to_read = LINEBUF_SIZE + more; + /* *end is where the next fgets will start putting data. + * There is a special case if we are already at end of input: + * *end is the '\n' added to the line since it didn't have one. + * We are here because the terminating null wouldn't fit. + */ + if (atEOF) { + end[1] = '\0'; + linesize++; + break; + } + token = linebuf; /* We may have a new buffer */ + } + } while (more); + nLines++; + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL2) { + fprintf(stderr, "\n===================NEWLINE=================\n\n"); + fprintf(stderr, "len = %d\nline = \"%s\"\n", (int)strlen(linebuf), linebuf); + } +#endif +} + +/* Advance through the input stream until either a non-whitespace character, a + * newline, or end of input is reached. + */ +static void +SkipWhitespace(void) +{ + while (*token) { + char ch = *token; + + if (isspace((int)ch)) + if (ch == '\n') /* Stop at end of line */ + return; + else + token++; + else if (ch == '#') { + token = &linebuf[linesize-1]; + return; + } + else + return; + } +} + +static int scanReadOnly; /* Set => don't modify input scanner */ +static int doingAccess; /* Set => parsing [access] section */ +static int tokenQuoted; /* True when token a quoted string */ + +/* Print the current token on a given stream. */ + +static void +PrintToken(FILE *stream) +{ + char *p; + if (tokenQuoted) + fputc('"', stream); + for (p = token; p < tokenend; p++) { + if (*p == '\n') + fputs("<newline>", stream); + else if (*p == '\0') + fputs("<null>", stream); + else + fputc(*p, stream); + } + if (tokenQuoted) + fputc('"', stream); +} + +/* Move to the next token in the input stream. This is done by skipping any + * non-whitespace characters to get to the end of the current token then + * skipping any whitespace and newline characters to get to the next token. + */ + +static void +FindNextToken(void) +{ + static char *rawToken; /* Used in pathological quoting case */ + char ch; + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL2) { + fprintf(stderr, "FindNextToken() "); + fprintf(stderr, "scanInit=%d scanError=%d scanReadOnly=%d doingAccess=%d tokenQuoted=%d token=%p tokenend=%p\n", scanInit, scanError, scanReadOnly, doingAccess, tokenQuoted, token, tokenend); + } +#endif + + do { + if (scanInit) { + if (*token == '\0') /* EOF is EOF and that's final */ + return; + if (scanError) /* Ditto for scanner errors */ + return; + + if (*token == '\n') /* If at end of line, get next line */ + GetNextLine(); + else { /* Otherwise move past "old" token */ + if (tokenQuoted) { /* Move past last quote */ + tokenend++; + tokenQuoted = 0; + } + token = tokenend; + } + SkipWhitespace(); /* Find null, newline or non-space */ + } + else { + scanInit = 1; + scanError = 0; + GetNextLine(); + SkipWhitespace(); /* Don't return yet, find tokenend */ + } + } while (doingAccess && *token == '\n'); + + /* Now we have the start of a token. Find the end. */ + + ch = *token; + if (ch == '\0' || ch == '\n') { + tokenend = token; + return; + } + + if (doingAccess) + if (ch == ',' || ch == ':' || ch == ';' || ch == '[' || ch == ']') { + tokenend = token + 1; + return; + } + + rawToken = token; /* Save real token start in case it moves */ + tokenend = token + 1; + if (ch == '#') /* For comments, token is newline */ + token = tokenend = &linebuf[linesize-1]; + else { + int inQuotes = *token == '"'; + int fixToken = 0; + + do { + int gotEnd = isspace((int)*tokenend); + + while (!gotEnd) { + switch (*tokenend) { + case '#': /* \# or # in quotes does not start a comment */ + if (*(tokenend - 1) == '\\' || inQuotes) + fixToken = 1; + else /* Comments don't need whitespace in front */ + gotEnd = 1; + break; + + case ',': + case ':': + case ';': + case '[': + case ']': + gotEnd = doingAccess && !inQuotes; + break; + + case '"': + if (*(tokenend - 1) == '\\') + fixToken = 1; + else { + if (inQuotes) { + inQuotes = 0; + gotEnd = 1; + } + } + break; + + default: + gotEnd = isspace((int)*tokenend); + } + if (gotEnd) + break; + tokenend++; + } + /* Skip any whitespace if still in quotes, but stop at end of line */ + if (inQuotes) + while (isspace((int)*tokenend) && *tokenend != '\n') + tokenend++; + } while (inQuotes && *tokenend != '\n'); + + if (inQuotes) { + scanError = 1; + *token = 0; + tokenend = token; + fprintf(stderr, + "pmcd config[line %d]: Error: unterminated quoted string\n", + nLines); + return; + } + + /* Replace any \# or \" in the token with # or " */ + if (fixToken && !scanReadOnly) { + char *p, *q; + + for (p = q = tokenend; p >= token; p--) { + if (*p == '\\' && ( p[1] == '#' || p[1] == '"') ) + continue; + *q-- = *p; + } + token = q + 1; + } + } + + /* If token originally started with a quote, token is what's inside quotes. + * Note that *rawToken is checked since *token will also be " if the + * token originally started with a \" that has been changed to ". + */ + if (*rawToken == '"') { + token++; + tokenQuoted = 1; + } + else + tokenQuoted = 0; + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL2) { + fputs("TOKEN = '", stderr); + PrintToken(stderr); + fputs("' ", stderr); + fprintf(stderr, "scanInit=%d scanError=%d scanReadOnly=%d doingAccess=%d tokenQuoted=%d token=%p tokenend=%p\n", scanInit, scanError, scanReadOnly, doingAccess, tokenQuoted, token, tokenend); + } +#endif +} + +/* Move to the next line of the input stream. */ + +static void +SkipLine(void) +{ + while (*token && *token != '\n') + FindNextToken(); + FindNextToken(); /* Move over the newline */ +} + +/* From an argv, build a command line suitable for display in logs etc. */ + +static char * +BuildCmdLine(char **argv) +{ + int i, cmdLen = 0; + char *cmdLine; + char *p; + + if (argv == NULL) + return NULL; + for (i = 0; argv[i] != NULL; i++) { + cmdLen += strlen(argv[i]) + 1; /* +1 for space separator or null */ + /* any arg with whitespace appears in quotes */ + if (strpbrk(argv[i], " \t") != NULL) + cmdLen += 2; + /* any quote gets a \ prepended */ + for (p = argv[i]; *p; p++) + if (*p == '"') + cmdLen++; + } + + if ((cmdLine = (char *)malloc(cmdLen)) == NULL) { + fprintf(stderr, "pmcd config[line %d]: Error: failed to build command line\n", + nLines); + __pmNoMem("pmcd config: BuildCmdLine", cmdLen, PM_RECOV_ERR); + return NULL; + } + for (i = 0, p = cmdLine; argv[i] != NULL; i++) { + int quote = strpbrk(argv[i], " \t") != NULL; + char *q; + + if (quote) + *p++ = '"'; + for (q = argv[i]; *q; q++) { + if (*q == '"') + *p++ = '\\'; + *p++ = *q; + } + if (quote) + *p++ = '"'; + if (argv[i+1] != NULL) + *p++ = ' '; + } + *p = '\0'; + return cmdLine; +} + + +/* Build an argument list suitable for an exec call from the rest of the tokens + * on the current line. + */ +char ** +BuildArgv(void) +{ + int nArgs; + char **result; + + nArgs = 0; + result = NULL; + do { + /* Make result big enough for new arg and terminating NULL pointer */ + result = (char **)realloc(result, (nArgs + 2) * sizeof(char *)); + if (result != NULL) { + if (*token != '/') + result[nArgs] = CopyToken(); + else if ((result[nArgs] = CopyToken()) != NULL) + __pmNativePath(result[nArgs]); + } + if (result == NULL || result[nArgs] == NULL) { + fprintf(stderr, "pmcd config[line %d]: Error: failed to build argument list\n", + nLines); + __pmNoMem("pmcd config: build argv", nArgs * sizeof(char *), + PM_RECOV_ERR); + if (result != NULL) { + while (nArgs >= 0) { + if (result[nArgs] != NULL) + free(result[nArgs]); + nArgs--; + } + free(result); + } + return NULL; + } +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL2) + fprintf(stderr, "argv[%d] = '%s'\n", nArgs, result[nArgs]); +#endif + + nArgs++; + FindNextToken(); + } while (*token && *token != '\n'); + result[nArgs] = NULL; + + return result; +} + +/* Return the next unused index into the agent array, extending the array + as necessary. */ +static AgentInfo * +GetNewAgent(void) +{ + AgentInfo *na; + + if (agent == NULL) { + agent = (AgentInfo*)malloc(sizeof(AgentInfo) * MIN_AGENTS_ALLOC); + if (agent == NULL) { + perror("GetNewAgentIndex: malloc"); + exit(1); + } + szAgents = MIN_AGENTS_ALLOC; + } + else if (nAgents >= szAgents) { + agent = (AgentInfo*) + realloc(agent, sizeof(AgentInfo) * 2 * szAgents); + if (agent == NULL) { + perror("GetNewAgentIndex: realloc"); + exit(1); + } + szAgents *= 2; + } + + na = agent+nAgents; nAgents++; + memset (na, 0, sizeof(AgentInfo)); + + return na; +} + +/* Free any malloc()-ed memory associated with an agent */ + +static void +FreeAgent(AgentInfo *ap) +{ + int i; + char **argv = NULL; + + free(ap->pmDomainLabel); + if (ap->ipcType == AGENT_DSO) { + free(ap->ipc.dso.pathName); + free(ap->ipc.dso.entryPoint); + } + else if (ap->ipcType == AGENT_SOCKET) { + if (ap->ipc.socket.commandLine != NULL) { + free(ap->ipc.socket.commandLine); + argv = ap->ipc.socket.argv; + } + } + else + if (ap->ipc.pipe.commandLine != NULL) { + free(ap->ipc.pipe.commandLine); + argv = ap->ipc.pipe.argv; + } + + if (argv != NULL) { + for (i = 0; argv[i] != NULL; i++) + free(argv[i]); + free(argv); + } +} + +/* Parse a DSO specification, creating and initialising a new entry in the + * agent table if the spec has no errors. + */ +static int +ParseDso(char *pmDomainLabel, int pmDomainId) +{ + char *pathName; + char *entryPoint; + AgentInfo *newAgent; + int xlatePath = 0; + + FindNextToken(); + if (*token == '\n') { + fprintf(stderr, "pmcd config[line %d]: Error: expected DSO entry point\n", nLines); + return -1; + } + if ((entryPoint = CopyToken()) == NULL) { + fprintf(stderr, "pmcd config[line %d]: Error: couldn't copy DSO entry point\n", + nLines); + __pmNoMem("pmcd config", tokenend - token + 1, PM_FATAL_ERR); + } + + FindNextToken(); + if (*token == '\n') { + fprintf(stderr, "pmcd config[line %d]: Error: expected DSO pathname\n", nLines); + free(entryPoint); + return -1; + } + if (*token != '/') { + if (token[strlen(token)-1] == '\n') + token[strlen(token)-1] = '\0'; + fprintf(stderr, "pmcd config[line %d]: Error: path \"%s\" to PMDA is not absolute\n", nLines, token); + free(entryPoint); + return -1; + } + + if ((pathName = CopyToken()) == NULL) { + fprintf(stderr, "pmcd config[line %d]: Error: couldn't copy DSO pathname\n", + nLines); + __pmNoMem("pmcd config", tokenend - token + 1, PM_FATAL_ERR); + } + __pmNativePath(pathName); + + FindNextToken(); + if (*token != '\n') { + fprintf(stderr, "pmcd config[line %d]: Error: too many parameters for DSO\n", + nLines); + free(entryPoint); + free(pathName); + return -1; + } + + /* Now create and initialise a slot in the agents table for the new agent */ + + newAgent = GetNewAgent(); + + newAgent->ipcType = AGENT_DSO; + newAgent->pmDomainId = pmDomainId; + newAgent->inFd = -1; + newAgent->outFd = -1; + newAgent->pmDomainLabel = strdup(pmDomainLabel); + newAgent->ipc.dso.pathName = pathName; + newAgent->ipc.dso.xlatePath = xlatePath; + newAgent->ipc.dso.entryPoint = entryPoint; + + return 0; +} + +/* Parse a socket specification, creating and initialising a new entry in the + * agent table if the spec has no errors. + */ +static int +ParseSocket(char *pmDomainLabel, int pmDomainId) +{ + int addrDomain, port = -1; + char *socketName = NULL; + AgentInfo *newAgent; + + FindNextToken(); + if (TokenIs("inet")) + addrDomain = AF_INET; + else if (TokenIs("ipv6")) + addrDomain = AF_INET6; + else if (TokenIs("unix")) + addrDomain = AF_UNIX; + else { + fprintf(stderr, + "pmcd config[line %d]: Error: expected socket address domain (`inet', `ipv6', or `unix')\n", + nLines); + return -1; + } + + FindNextToken(); + if (*token == '\n') { + fprintf(stderr, "pmcd config[line %d]: Error: expected socket port name or number\n", + nLines); + return -1; + } + else if (TokenIsNumber()) + port = TokenNumVal(); + else + if ((socketName = CopyToken()) == NULL) { + fprintf(stderr, "pmcd config[line %d]: Error: couldn't copy port name\n", + nLines); + __pmNoMem("pmcd config", tokenend - token + 1, PM_FATAL_ERR); + } + FindNextToken(); + + /* If an internet domain port name was specified, find the corresponding + port number. */ + + if ((addrDomain == AF_INET || addrDomain == AF_INET6) && socketName) { + struct servent *service; + + service = getservbyname(socketName, NULL); + if (service) + port = service->s_port; + else { + fprintf(stderr, + "pmcd config[line %d]: Error: failed to get port number for port name %s\n", + nLines, socketName); + free(socketName); + return -1; + } + } + + /* Now create and initialise a slot in the agents table for the new agent */ + + newAgent = GetNewAgent(); + + newAgent->ipcType = AGENT_SOCKET; + newAgent->pmDomainId = pmDomainId; + newAgent->inFd = -1; + newAgent->outFd = -1; + newAgent->pmDomainLabel = strdup(pmDomainLabel); + newAgent->ipc.socket.addrDomain = addrDomain; + newAgent->ipc.socket.name = socketName; + newAgent->ipc.socket.port = port; + if (*token != '\n') { + newAgent->ipc.socket.argv = BuildArgv(); + if (newAgent->ipc.socket.argv == NULL) { + fprintf(stderr, "pmcd config[line %d]: Error: building argv for \"%s\" agent.\n", + nLines, newAgent->pmDomainLabel); + FreeAgent(newAgent); + nAgents--; + return -1; + } + newAgent->ipc.socket.commandLine = BuildCmdLine(newAgent->ipc.socket.argv); + } + newAgent->ipc.socket.agentPid = (pid_t)-1; + + return 0; +} + +/* Parse a pipe specification, creating and initialising a new entry in the + * agent table if the spec has no errors. + */ +static int +ParsePipe(char *pmDomainLabel, int pmDomainId) +{ + int i; + AgentInfo *newAgent; + int notReady = 0; + + FindNextToken(); + if (!TokenIs("binary")) { + fprintf(stderr, + "pmcd config[line %d]: Error: pipe PDU type expected (`binary')\n", + nLines); + return -1; + } + + do { + i = 0; + FindNextToken(); + if (*token == '\n') { + fprintf(stderr, + "pmcd config[line %d]: Error: command to create pipe agent expected.\n", + nLines); + return -1; + } else if ((i = TokenIs ("notready"))) { + notReady = 1; + } + } while (i); + + /* Now create and initialise a slot in the agents table for the new agent */ + + newAgent = GetNewAgent(); + newAgent->ipcType = AGENT_PIPE; + newAgent->pmDomainId = pmDomainId; + newAgent->inFd = -1; + newAgent->outFd = -1; + newAgent->pmDomainLabel = strdup(pmDomainLabel); + newAgent->status.startNotReady = notReady; + newAgent->ipc.pipe.argv = BuildArgv(); + + if (newAgent->ipc.pipe.argv == NULL) { + fprintf(stderr, "pmcd config[line %d]: Error: building argv for \"%s\" agent.\n", + nLines, newAgent->pmDomainLabel); + FreeAgent(newAgent); + nAgents--; + return -1; + } + newAgent->ipc.pipe.commandLine = BuildCmdLine(newAgent->ipc.pipe.argv); + + return 0; +} + +static int +ParseAccessSpec(int allow, int *specOps, int *denyOps, int *maxCons, int recursion) +{ + int op; /* >0 for specific ops, 0 otherwise */ + int haveOps = 0, haveAll = 0; + int haveComma = 0; + + if (*token == ';') { + fprintf(stderr, "pmcd config[line %d]: Error: empty or incomplete permissions list\n", + nLines); + return -1; + } + + if (!recursion) /* Set maxCons to unspecified 1st time */ + *maxCons = 0; + while (*token && *token != ';') { + op = 0; + if (TokenIs("fetch")) + op = PMCD_OP_FETCH; + else if (TokenIs("store")) + op = PMCD_OP_STORE; + else if (TokenIs("all")) { + if (haveOps) { + fprintf(stderr, + "pmcd config[line %d]: Error: can't have \"all\" mixed with specific permissions\n", + nLines); + return -1; + } + haveAll = 1; + if (recursion) { + fprintf(stderr, + "pmcd config[line %d]: Error: can't have \"all\" within an \"all except\"\n", + nLines); + return -1; + } + FindNextToken(); + + /* Any "all" statement specifies permissions for all operations + * Start off with all operations in allow/disallowed state + */ + *denyOps = allow ? PMCD_OP_NONE : PMCD_OP_ALL; + + if (TokenIs("except")) { + /* Now deal with exceptions by reversing the "allow" sense */ + int sts; + + FindNextToken(); + sts = ParseAccessSpec(!allow, specOps, denyOps, maxCons, 1); + if (sts < 0) return -1; + } + *specOps = PMCD_OP_ALL; /* Do this AFTER any recursive call */ + } + else if (TokenIs("maximum") || TokenIs("unlimited")) { + int unlimited = (*token == 'u' || *token == 'U'); + + if (*maxCons) { + fprintf(stderr, + "pmcd config[line %d]: Error: connection limit already specified\n", + nLines); + return -1; + } + if (recursion && !haveOps) { + fprintf(stderr, + "pmcd config[line %d]: Error: connection limit may not immediately follow \"all except\"\n", + nLines); + return -1; + } + + /* "maximum N connections" or "unlimited connections" is not + * allowed in a disallow statement. This is a bit tricky, because + * of the recursion in "all except", which flips "allow" into + * !"allow" and recursion from 0 to 1 for the recursive call to + * this function. The required test is !XOR: "!recursion && allow" + * is an "allow" with no "except". "recursion && !allow" is an + * "allow" with an "except" anything else is a "disallow" (i.e. an + * error) + */ + if (!(recursion ^ allow)) { /* disallow statement */ + fprintf(stderr, + "pmcd config[line %d]: Error: can't specify connection limit in a disallow statement\n", + nLines); + return -1; + } + if (unlimited) + *maxCons = -1; + else { + FindNextToken(); + if (!TokenIsNumber() || TokenNumVal() <= 0) { + fprintf(stderr, + "pmcd config[line %d]: Error: maximum connection limit must be a positive number\n", + nLines); + return -1; + } + *maxCons = TokenNumVal(); + FindNextToken(); + } + if (!TokenIs("connections")) { + fprintf(stderr, + "pmcd config[line %d]: Error: \"connections\" expected\n", + nLines); + return -1; + } + FindNextToken(); + } + else { + fprintf(stderr, "pmcd config[line %d]: Error: bad access specifier\n", + nLines); + return -1; + } + + /* If there was a specific operation mentioned, (dis)allow it */ + if (op) { + if (haveAll) { + fprintf(stderr, + "pmcd config[line %d]: Error: can't have \"all\" mixed with specific permissions\n", + nLines); + return -1; + } + haveOps = 1; + *specOps |= op; + if (allow) + *denyOps &= (~op); + else + *denyOps |= op; + FindNextToken(); + } + if (*token != ',' && *token != ';') { + fprintf(stderr, + "pmcd config[line %d]: Error: ',' or ';' expected in permission list\n", + nLines); + return -1; + } + if (*token == ',') { + haveComma = 1; + FindNextToken(); + } + else + haveComma = 0; + } + if (haveComma) { + fprintf(stderr, + "pmcd config[line %d]: Error: misplaced (trailing) ',' in permission list\n", + nLines); + return -1; + } + return 0; +} + +static int +ParseNames(char ***namesp, const char *nametype) +{ + static char **names; + static int szNames; + int nnames = 0; + int another = 1; + + /* Beware of quoted tokens of length longer than 1. e.g. ":*" */ + while (*token && another && + ((tokenend - token > 1) || (*token != ':' && *token != ';'))) { + if (nnames == szNames) { + int need; + + szNames += 8; + need = szNames * (int)sizeof(char**); + if ((names = (char **)realloc(names, need)) == NULL) + __pmNoMem("pmcd ParseNames name list", need, PM_FATAL_ERR); + } + if ((names[nnames++] = CopyToken()) == NULL) + __pmNoMem("pmcd ParseNames name", tokenend - token, PM_FATAL_ERR); + FindNextToken(); + if (*token != ',' && *token != ':') { + fprintf(stderr, + "pmcd config[line %d]: Error: ',' or ':' expected after \"%s\"\n", + nLines, names[nnames-1]); + return -1; + } + if (*token == ',') { + FindNextToken(); + another = 1; + } + else + another = 0; + } + if (nnames == 0) { + fprintf(stderr, + "pmcd config[line %d]: Error: no %ss in allow/disallow statement\n", + nLines, nametype); + return -1; + } + if (another) { + fprintf(stderr, "pmcd config[line %d]: Error: %s expected after ','\n", + nLines, nametype); + return -1; + } + if (*token != ':') { + fprintf(stderr, "pmcd config[line %d]: Error: ':' expected after \"%s\"\n", + nLines, names[nnames-1]); + return -1; + } + *namesp = names; + return nnames; +} + +static int +ParseHosts(int allow) +{ + int sts; + int nhosts; + int i; + int ok = 0; + int specOps = 0; + int denyOps = 0; + int maxCons = 0; /* Zero=>unspecified, -1=>unlimited */ + char **hostnames; + + if ((nhosts = ParseNames(&hostnames, "host")) < 0) + goto error; + + FindNextToken(); + if (ParseAccessSpec(allow, &specOps, &denyOps, &maxCons, 0) < 0) + goto error; + + if (pmDebug & DBG_TRACE_APPL1) { + for (i = 0; i < nhosts; i++) + fprintf(stderr, "HOST ACCESS: %s specOps=%02x denyOps=%02x maxCons=%d\n", + hostnames[i], specOps, denyOps, maxCons); + } + + /* Make new entries for hosts in host access list */ + for (i = 0; i < nhosts; i++) { + if ((sts = __pmAccAddHost(hostnames[i], specOps, denyOps, maxCons)) < 0) { + if (sts == -EHOSTUNREACH || sts == -EHOSTDOWN) + fprintf(stderr, "Warning: the following access control specification will be ignored\n"); + fprintf(stderr, + "pmcd config[line %d]: Warning: access control error for host '%s': %s\n", + nLines, hostnames[i], pmErrStr(sts)); + if (sts == -EHOSTUNREACH || sts == -EHOSTDOWN) + ; + else + goto error; + } + else + ok = 1; + } + return ok; + +error: + for (i = 0; i < nhosts; i++) + free(hostnames[i]); + return -1; +} + +static int +ParseUsers(int allow) +{ + int sts; + int nusers; + int i; + int ok = 0; + int specOps = 0; + int denyOps = 0; + int maxCons = 0; /* Zero=>unspecified, -1=>unlimited */ + char **usernames; + + if ((nusers = ParseNames(&usernames, "user")) < 0) + goto error; + + FindNextToken(); + if (ParseAccessSpec(allow, &specOps, &denyOps, &maxCons, 0) < 0) + goto error; + + if (pmDebug & DBG_TRACE_APPL1) { + for (i = 0; i < nusers; i++) + fprintf(stderr, "USER ACCESS: %s specOps=%02x denyOps=%02x maxCons=%d\n", + usernames[i], specOps, denyOps, maxCons); + } + + /* Make new entries for users in user access list */ + for (i = 0; i < nusers; i++) { + if ((sts = __pmAccAddUser(usernames[i], specOps, denyOps, maxCons)) < 0) { + fprintf(stderr, + "pmcd config[line %d]: Warning: access control error for user '%s': %s\n", + nLines, usernames[i], pmErrStr(sts)); + goto error; + } + ok = 1; + } + return ok; + +error: + for (i = 0; i < nusers; i++) + free(usernames[i]); + return -1; +} + +static int +ParseGroups(int allow) +{ + int sts; + int ngroups; + int i; + int ok = 0; + int specOps = 0; + int denyOps = 0; + int maxCons = 0; /* Zero=>unspecified, -1=>unlimited */ + char **groupnames; + + if ((ngroups = ParseNames(&groupnames, "group")) < 0) + goto error; + + FindNextToken(); + if (ParseAccessSpec(allow, &specOps, &denyOps, &maxCons, 0) < 0) + goto error; + + if (pmDebug & DBG_TRACE_APPL1) { + for (i = 0; i < ngroups; i++) + fprintf(stderr, "GROUP ACCESS: %s specOps=%02x denyOps=%02x maxCons=%d\n", + groupnames[i], specOps, denyOps, maxCons); + } + + /* Make new entries for groups in group access list */ + for (i = 0; i < ngroups; i++) { + if ((sts = __pmAccAddGroup(groupnames[i], specOps, denyOps, maxCons)) < 0) { + fprintf(stderr, + "pmcd config[line %d]: Warning: access control error for group '%s': %s\n", + nLines, groupnames[i], pmErrStr(sts)); + goto error; + } + ok = 1; + } + return ok; + +error: + for (i = 0; i < ngroups; i++) + free(groupnames[i]); + return -1; +} + +static int +ParseAccessControls(void) +{ + int sts = 0; + int tmp; + int allow; + int naccess = 0; + int need_creds = 0; + + doingAccess = 1; + /* This gets a little tricky, because the token may be "[access]", or + * "[access" or "[". "[" and "]" can't be made special characters until + * the scanner knows it is in the access control section because the arg + * lists for agents may contain them. + */ + if (TokenIs("[access]")) + FindNextToken(); + else { + if (TokenIs("[")) { + FindNextToken(); + if (!TokenIs("access")) { + fprintf(stderr, + "pmcd config[line %d]: Error: \"access\" keyword expected\n", + nLines); + return -1; + } + } + else if (!TokenIs("[access")) { + fprintf(stderr, + "pmcd config[line %d]: Error: \"access\" keyword expected\n", + nLines); + return -1; + } + FindNextToken(); + if (*token != ']') { + fprintf(stderr, "pmcd config[line %d]: Error: ']' expected\n", nLines); + return -1; + } + FindNextToken(); + } + while (*token && !scanError) { + if (TokenIs("allow")) + allow = 1; + else if (TokenIs("disallow")) + allow = 0; + else { + fprintf(stderr, + "pmcd config[line %d]: Error: allow or disallow statement expected\n", + nLines); + sts = -1; + while (*token && !scanError && *token != ';') + FindNextToken(); + if (*token && !scanError && *token == ';') { + FindNextToken(); + continue; + } + return -1; + } + FindNextToken(); + if (TokenIs("user") || TokenIs("users")) { + FindNextToken(); + if ((tmp = ParseUsers(allow)) < 0) + sts = -1; + else + need_creds = 1; + } else if (TokenIs("group") || TokenIs("groups")) { + FindNextToken(); + if ((tmp = ParseGroups(allow)) < 0) + sts = -1; + else + need_creds = 1; + } else if (TokenIs("host") || TokenIs("hosts")) { + FindNextToken(); + if ((tmp = ParseHosts(allow)) < 0) + sts = -1; + } else { + if ((tmp = ParseHosts(allow)) < 0) + sts = -1; + } + if (tmp > 0) + naccess++; + while (*token && !scanError && *token != ';') + FindNextToken(); + if (!*token || scanError) + return -1; + FindNextToken(); + } + if (sts != 0) + return sts; + + if (naccess == 0) { + fprintf(stderr, + "pmcd config[line %d]: Error: no valid statements in [access] section\n", + nLines); + return -1; + } + + if (need_creds) + __pmServerSetFeature(PM_SERVER_FEATURE_CREDS_REQD); + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL1) + __pmAccDumpLists(stderr); +#endif + + return 0; +} + +/* Parse the configuration file, creating the agent list. */ +static int +ReadConfigFile(FILE *configFile) +{ + char *pmDomainLabel = NULL; + int i, pmDomainId; + int sts = 0; + + inputStream = configFile; + scanInit = 0; + scanError = 0; + doingAccess = 0; + nLines = 0; + FindNextToken(); + while (*token && !scanError) { + if (*token == '\n') /* It's a comment or blank line */ + goto doneLine; + + if (*token == '[') /* Start of access control specs */ + break; + + if ((pmDomainLabel = CopyToken()) == NULL) + __pmNoMem("pmcd config: domain label", tokenend - token + 1, PM_FATAL_ERR); + + FindNextToken(); + if (TokenIsNumber()) { + pmDomainId = TokenNumVal(); + FindNextToken(); + } + else { + fprintf(stderr, + "pmcd config[line %d]: Error: expected domain number for \"%s\" agent\n", + nLines, pmDomainLabel); + sts = -1; + goto doneLine; + } + if (pmDomainId < 0 || pmDomainId > MAXDOMID) { + fprintf(stderr, + "pmcd config[line %d]: Error: Illegal domain number (%d) for \"%s\" agent\n", + nLines, pmDomainId, pmDomainLabel); + sts = -1; + goto doneLine; + } + /* Can't use mapdom because it isn't built yet. Can't build it during + * parsing because this might be a restart parse that fails, requiring + * a revert to the old mapdom. + */ + for (i = 0; i < nAgents; i++) + if (pmDomainId == agent[i].pmDomainId) { + fprintf(stderr, + "pmcd config[line %d]: Error: domain number for \"%s\" agent clashes with \"%s\" agent\n", + nLines, pmDomainLabel, agent[i].pmDomainLabel); + sts = -1; + goto doneLine; + } + + /* + * ParseXXX routines must return + * -1 for failure and ensure a NewAgent structure has NOT been + * allocated + * 0 for success with a NewAgent structure allocated + */ + if (TokenIs("dso")) + sts = ParseDso(pmDomainLabel, pmDomainId); + else if (TokenIs("socket")) + sts = ParseSocket(pmDomainLabel, pmDomainId); + else if (TokenIs("pipe")) + sts = ParsePipe(pmDomainLabel, pmDomainId); + else { + fprintf(stderr, + "pmcd config[line %d]: Error: expected `dso', `socket' or `pipe'\n", + nLines); + sts = -1; + } +doneLine: + if (pmDomainLabel != NULL) { + free(pmDomainLabel); + pmDomainLabel = NULL; + } + SkipLine(); + } + if (scanError) { + fprintf(stderr, "pmcd config: Can't continue, giving up\n"); + sts = -1; + } + if (*token == '[' && sts != -1) + if (ParseAccessControls() < 0) + sts = -1; + return sts; +} + +static int +DoAuthentication(AgentInfo *ap, int clientID) +{ + int sts = 0; + __pmHashCtl *attrs = &client[clientID].attrs; + __pmHashNode *node; + + if ((ap->status.flags & PDU_FLAG_AUTH) == 0) + return 0; + + if (ap->ipcType == AGENT_DSO) { + if (ap->ipc.dso.dispatch.comm.pmda_interface < PMDA_INTERFACE_6 || + ap->ipc.dso.dispatch.version.six.attribute == NULL) + return 0; + for (node = __pmHashWalk(attrs, PM_HASH_WALK_START); + node != NULL; + node = __pmHashWalk(attrs, PM_HASH_WALK_NEXT)) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) { + char buffer[64]; + __pmAttrStr_r(node->key, node->data, buffer, sizeof(buffer)); + fprintf(stderr, "pmcd: send client[%d] attr %s to dso agent[%d]", + clientID, buffer, (int)(ap - agent)); + } +#endif + if ((sts = ap->ipc.dso.dispatch.version.six.attribute( + clientID, node->key, node->data, + node->data ? strlen(node->data)+1 : 0, + ap->ipc.dso.dispatch.version.six.ext)) < 0) + break; + } + } else { + /* daemon PMDA ... ship attributes */ + if (ap->status.notReady) + return PM_ERR_AGAIN; + for (node = __pmHashWalk(attrs, PM_HASH_WALK_START); + node != NULL; + node = __pmHashWalk(attrs, PM_HASH_WALK_NEXT)) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) { + char buffer[64]; + __pmAttrStr_r(node->key, node->data, buffer, sizeof(buffer)); + fprintf(stderr, "pmcd: send client[%d] attr %s to daemon agent[%d]", + clientID, buffer, (int)(ap - agent)); + } +#endif + if ((sts = __pmSendAuth(ap->inFd, + clientID, node->key, node->data, + node->data ? strlen(node->data)+1 : 0)) < 0) + break; + } + } + return sts; +} + +/* + * Once a secure client arrives, we need to inform any interested PMDAs. + * Iterate over the authenticating agents and send connection attributes. + */ +int +AgentsAuthentication(int clientID) +{ + int agentID, sts = 0; + + for (agentID = 0; agentID < nAgents; agentID++) { + if (agent[agentID].status.connected && + (sts = DoAuthentication(&agent[agentID], clientID)) < 0) + break; + } + return sts; +} + +/* + * Once a PMDA has started, we need to inform it about secure clients. + * Iterate over the authenticated clients and send connection attributes + */ +int +ClientsAuthentication(AgentInfo *ap) +{ + int clientID, sts = 0; + + for (clientID = 0; clientID < nClients; clientID++) { + if (client[clientID].status.connected && + (sts = DoAuthentication(ap, clientID)) < 0) + break; + } + return sts; +} + +static int +DoAgentCreds(AgentInfo* aPtr, __pmPDU *pb) +{ + int i; + int sts = 0; + int flags = 0; + int sender = 0; + int credcount = 0; + int version = UNKNOWN_VERSION; + __pmCred *credlist = NULL; + __pmVersionCred *vcp; + + if ((sts = __pmDecodeCreds(pb, &sender, &credcount, &credlist)) < 0) + return sts; + pmcd_trace(TR_RECV_PDU, aPtr->outFd, sts, (int)((__psint_t)pb & 0xffffffff)); + + for (i = 0; i < credcount; i++) { + switch (credlist[i].c_type) { + case CVERSION: + vcp = (__pmVersionCred *)&credlist[i]; + aPtr->pduVersion = version = vcp->c_version; + aPtr->status.flags = flags = vcp->c_flags; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) + fprintf(stderr, "pmcd: version creds (version=%u,flags=%x)\n", + aPtr->pduVersion, aPtr->status.flags); +#endif + break; + } + } + + if (credlist != NULL) + free(credlist); + + if (((sts = __pmSetVersionIPC(aPtr->inFd, version)) < 0) || + ((sts = __pmSetVersionIPC(aPtr->outFd, version)) < 0)) + return sts; + + if (version != UNKNOWN_VERSION) { /* finish the version exchange */ + __pmVersionCred handshake; + __pmCred *cp = (__pmCred *)&handshake; + + /* return pmcd PDU version and all flags pmcd knows about */ + handshake.c_type = CVERSION; + handshake.c_version = PDU_VERSION; + handshake.c_flags = (flags & PDU_FLAG_AUTH); + if ((sts = __pmSendCreds(aPtr->inFd, (int)getpid(), 1, cp)) < 0) + return sts; + pmcd_trace(TR_XMIT_PDU, aPtr->inFd, PDU_CREDS, credcount); + + /* send auth attributes for existing connected clients */ + if ((flags & PDU_FLAG_AUTH) != 0 && + (sts = ClientsAuthentication(aPtr)) < 0) + return sts; + } + + return 0; +} + +/* version exchange - get a credentials PDU from 2.0 agents */ +static int +AgentNegotiate(AgentInfo *aPtr) +{ + int sts; + __pmPDU *ack; + + sts = __pmGetPDU(aPtr->outFd, ANY_SIZE, _creds_timeout, &ack); + if (sts == PDU_CREDS) { + if ((sts = DoAgentCreds(aPtr, ack)) < 0) { + fprintf(stderr, "pmcd: version exchange failed " + "for \"%s\" agent: %s\n", aPtr->pmDomainLabel, pmErrStr(sts)); + } + __pmUnpinPDUBuf(ack); + return sts; + } + + if (sts > 0) { + fprintf(stderr, "pmcd: unexpected PDU type (0x%x) at initial " + "exchange with %s PMDA\n", sts, aPtr->pmDomainLabel); + __pmUnpinPDUBuf(ack); + } + else if (sts == 0) + fprintf(stderr, "pmcd: unexpected end-of-file at initial " + "exchange with %s PMDA\n", aPtr->pmDomainLabel); + else + fprintf(stderr, "pmcd: error at initial PDU exchange with " + "%s PMDA: %s\n", aPtr->pmDomainLabel, pmErrStr(sts)); + return PM_ERR_IPC; +} + +/* Connect to an agent's socket. */ +static int +ConnectSocketAgent(AgentInfo *aPtr) +{ + int sts = 0; + int fd = -1; /* pander to gcc */ + + if (aPtr->ipc.socket.addrDomain == AF_INET || aPtr->ipc.socket.addrDomain == AF_INET6) { + __pmSockAddr *addr; + __pmHostEnt *host; + void *enumIx; + + if ((host = __pmGetAddrInfo("localhost")) == NULL) { + fputs("pmcd: Error getting inet address for localhost\n", stderr); + goto error; + } + enumIx = NULL; + for (addr = __pmHostEntGetSockAddr(host, &enumIx); + addr != NULL; + addr = __pmHostEntGetSockAddr(host, &enumIx)) { + if (__pmSockAddrIsInet(addr)) { + /* Only consider addresses of the chosen family. */ + if (aPtr->ipc.socket.addrDomain != AF_INET) + continue; + fd = __pmCreateSocket(); + } + else if (__pmSockAddrIsIPv6(addr)) { + /* Only consider addresses of the chosen family. */ + if (aPtr->ipc.socket.addrDomain != AF_INET6) + continue; + fd = __pmCreateIPv6Socket(); + } + else { + fprintf(stderr, + "pmcd: Error creating socket for \"%s\" agent : invalid address family %d\n", + aPtr->pmDomainLabel, __pmSockAddrGetFamily(addr)); + fd = -1; + } + if (fd < 0) { + __pmSockAddrFree(addr); + continue; /* Try the next address */ + } + + __pmSockAddrSetPort(addr, aPtr->ipc.socket.port); + sts = __pmConnect(fd, (void *)addr, __pmSockAddrSize()); + __pmSockAddrFree(addr); + + if (sts == 0) + break; /* good connection */ + + /* Unsuccessful connection. */ + __pmCloseSocket(fd); + fd = -1; + } + __pmHostEntFree(host); + } + else { +#if defined(HAVE_STRUCT_SOCKADDR_UN) + struct sockaddr_un addr; + int len; + + fd = socket(aPtr->ipc.socket.addrDomain, SOCK_STREAM, 0); + if (fd < 0) { + fprintf(stderr, + "pmcd: Error creating socket for \"%s\" agent : %s\n", + aPtr->pmDomainLabel, netstrerror()); + return -1; + } + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strcpy(addr.sun_path, aPtr->ipc.socket.name); + len = (int)offsetof(struct sockaddr_un, sun_path) + (int)strlen(addr.sun_path); + sts = connect(fd, (struct sockaddr *) &addr, len); +#else + fprintf(stderr, "pmcd: UNIX sockets are not supported : \"%s\" agent\n", + aPtr->pmDomainLabel); + goto error; +#endif + } + if (sts < 0) { + fprintf(stderr, "pmcd: Error connecting to \"%s\" agent : %s\n", + aPtr->pmDomainLabel, netstrerror()); + goto error; + } + aPtr->outFd = aPtr->inFd = fd; /* Sockets are bi-directional */ + pmcd_openfds_sethi(fd); + + if ((sts = AgentNegotiate(aPtr)) < 0) + goto error; + + return 0; + +error: + if (fd != -1) { + if (aPtr->ipc.socket.addrDomain == AF_INET || aPtr->ipc.socket.addrDomain == AF_INET6) + __pmCloseSocket(fd); + else + close(fd); + } + return -1; +} + +#ifndef IS_MINGW +static pid_t +CreateAgentPOSIX(AgentInfo *aPtr) +{ + int i; + int inPipe[2]; /* Pipe for input to child */ + int outPipe[2]; /* For output to child */ + pid_t childPid = (pid_t)-1; + char **argv = NULL; + + if (aPtr->ipcType == AGENT_PIPE) { + argv = aPtr->ipc.pipe.argv; + if (pipe1(inPipe) < 0) { + fprintf(stderr, + "pmcd: input pipe create failed for \"%s\" agent: %s\n", + aPtr->pmDomainLabel, osstrerror()); + return (pid_t)-1; + } + + if (pipe1(outPipe) < 0) { + fprintf(stderr, + "pmcd: output pipe create failed for \"%s\" agent: %s\n", + aPtr->pmDomainLabel, osstrerror()); + close(inPipe[0]); + close(inPipe[1]); + return (pid_t)-1; + } + pmcd_openfds_sethi(outPipe[1]); + } + else if (aPtr->ipcType == AGENT_SOCKET) + argv = aPtr->ipc.socket.argv; + + if (argv != NULL) { /* Start a new agent if required */ + childPid = fork(); + if (childPid == (pid_t)-1) { + fprintf(stderr, "pmcd: creating child for \"%s\" agent: %s\n", + aPtr->pmDomainLabel, osstrerror()); + if (aPtr->ipcType == AGENT_PIPE) { + close(inPipe[0]); + close(inPipe[1]); + close(outPipe[0]); + close(outPipe[1]); + } + return (pid_t)-1; + } + + if (childPid) { + /* This is the parent (PMCD) */ + if (aPtr->ipcType == AGENT_PIPE) { + close(inPipe[0]); + close(outPipe[1]); + aPtr->inFd = inPipe[1]; + aPtr->outFd = outPipe[0]; + } + } + else { + /* + * This is the child (new agent) + * make sure stderr is fd 2 + */ + dup2(fileno(stderr), STDERR_FILENO); + if (aPtr->ipcType == AGENT_PIPE) { + /* make pipe stdin for PMDA */ + dup2(inPipe[0], STDIN_FILENO); + /* make pipe stdout for PMDA */ + dup2(outPipe[1], STDOUT_FILENO); + } + else { + /* + * not a pipe, close stdin and attach stdout to stderr + */ + close(STDIN_FILENO); + dup2(STDERR_FILENO, STDOUT_FILENO); + } + + for (i = 0; i <= pmcd_hi_openfds; i++) { + /* Close all except std{in,out,err} */ + if (i == STDIN_FILENO || + i == STDOUT_FILENO || + i == STDERR_FILENO) + continue; + close(i); + } + + execvp(argv[0], argv); + /* botch if reach here */ + fprintf(stderr, "pmcd: error starting %s: %s\n", + argv[0], osstrerror()); + /* avoid atexit() processing, so _exit not exit */ + _exit(1); + } + } + return childPid; +} + +#else + +static pid_t +CreateAgentWin32(AgentInfo *aPtr) +{ + SECURITY_ATTRIBUTES saAttr; + PROCESS_INFORMATION piProcInfo; + STARTUPINFO siStartInfo; + HANDLE hChildStdinRd, hChildStdinWr, hChildStdoutRd, hChildStdoutWr; + BOOL bSuccess = FALSE; + LPTSTR command = NULL; + + if (aPtr->ipcType == AGENT_PIPE) + command = (LPTSTR)aPtr->ipc.pipe.commandLine; + else if (aPtr->ipcType == AGENT_SOCKET) + command = (LPTSTR)aPtr->ipc.socket.commandLine; + + // Set the bInheritHandle flag so pipe handles are inherited + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + saAttr.lpSecurityDescriptor = NULL; + + // Create a pipe for the child process's STDOUT. + if (!CreatePipe(&hChildStdoutRd, &hChildStdoutWr, &saAttr, 0)) { + fprintf(stderr, "pmcd: stdout CreatePipe failed, \"%s\" agent: %s\n", + aPtr->pmDomainLabel, osstrerror()); + return (pid_t)-1; + } + // Ensure the read handle to the pipe for STDOUT is not inherited. + if (!SetHandleInformation(hChildStdoutRd, HANDLE_FLAG_INHERIT, 0)) { + fprintf(stderr, "pmcd: stdout SetHandleInformation, \"%s\" agent: %s\n", + aPtr->pmDomainLabel, osstrerror()); + return (pid_t)-1; + } + + // Create a pipe for the child process's STDIN. + if (!CreatePipe(&hChildStdinRd, &hChildStdinWr, &saAttr, 0)) { + fprintf(stderr, "pmcd: stdin CreatePipe failed, \"%s\" agent: %s\n", + aPtr->pmDomainLabel, osstrerror()); + return (pid_t)-1; + } + // Ensure the write handle to the pipe for STDIN is not inherited. + if (!SetHandleInformation(hChildStdinWr, HANDLE_FLAG_INHERIT, 0)) { + fprintf(stderr, "pmcd: stdin SetHandleInformation, \"%s\" agent: %s\n", + aPtr->pmDomainLabel, osstrerror()); + return (pid_t)-1; + } + + // Create the child process. + + ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION)); + ZeroMemory(&siStartInfo, sizeof(STARTUPINFO)); + siStartInfo.cb = sizeof(STARTUPINFO); + siStartInfo.hStdOutput = hChildStdoutWr; + siStartInfo.hStdError = hChildStdoutWr; + siStartInfo.hStdInput = hChildStdinRd; + siStartInfo.dwFlags |= STARTF_USESTDHANDLES; + + bSuccess = CreateProcess(NULL, command, + NULL, // process security attributes + NULL, // primary thread security attributes + TRUE, // handles are inherited + 0, // creation flags + NULL, // use parent's environment + NULL, // use parent's current directory + &siStartInfo, // STARTUPINFO pointer + &piProcInfo); // receives PROCESS_INFORMATION + if (!bSuccess) { + fprintf(stderr, "pmcd: CreateProcess for \"%s\" agent: %s: %s\n", + aPtr->pmDomainLabel, command, osstrerror()); + return (pid_t)-1; + } + + aPtr->inFd = _open_osfhandle((intptr_t)hChildStdinRd, _O_WRONLY); + aPtr->outFd = _open_osfhandle((intptr_t)hChildStdoutWr, _O_RDONLY); + pmcd_openfds_sethi(aPtr->outFd); + + CloseHandle(piProcInfo.hProcess); + CloseHandle(piProcInfo.hThread); + CloseHandle(hChildStdoutRd); + CloseHandle(hChildStdinWr); + return piProcInfo.dwProcessId; +} +#endif + +/* Create the specified agent running at the end of a pair of pipes. */ +static int +CreateAgent(AgentInfo *aPtr) +{ + pid_t childPid; + int sts; + + fflush(stderr); + fflush(stdout); + +#ifdef IS_MINGW + childPid = CreateAgentWin32(aPtr); +#else + childPid = CreateAgentPOSIX(aPtr); +#endif + if (childPid < 0) + return (int)childPid; + + aPtr->status.isChild = 1; + if (aPtr->ipcType == AGENT_PIPE) { + aPtr->ipc.pipe.agentPid = childPid; + /* ready for version negotiation */ + if ((sts = AgentNegotiate(aPtr)) < 0) { + close(aPtr->inFd); + close(aPtr->outFd); + return sts; + } + } + else if (aPtr->ipcType == AGENT_SOCKET) + aPtr->ipc.socket.agentPid = childPid; + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) + fprintf(stderr, "pmcd: started PMDA %s (%d), pid=%" FMT_PID "\n", + aPtr->pmDomainLabel, aPtr->pmDomainId, childPid); +#endif + return 0; +} + +/* Print a table of all of the agent configuration info on a given stream. */ +void +PrintAgentInfo(FILE *stream) +{ + int i, version; + AgentInfo *aPtr; + + fputs("\nactive agent dom pid in out ver protocol parameters\n", stream); + fputs( "============ === ===== === === === ======== ==========\n", stream); + for (i = 0; i < nAgents; i++) { + aPtr = &agent[i]; + if (aPtr->status.connected == 0) + continue; + fprintf(stream, "%-12s", aPtr->pmDomainLabel); + + switch (aPtr->ipcType) { + case AGENT_DSO: + fprintf(stream, " %3d %3d dso i:%d", + aPtr->pmDomainId, + aPtr->ipc.dso.dispatch.comm.pmapi_version, + aPtr->ipc.dso.dispatch.comm.pmda_interface); + fprintf(stream, " lib=%s entry=%s [" PRINTF_P_PFX "%p]\n", + aPtr->ipc.dso.pathName, aPtr->ipc.dso.entryPoint, + aPtr->ipc.dso.initFn); + break; + + case AGENT_SOCKET: + version = __pmVersionIPC(aPtr->inFd); + fprintf(stream, " %3d %5" FMT_PID " %3d %3d %3d ", + aPtr->pmDomainId, aPtr->ipc.socket.agentPid, aPtr->inFd, aPtr->outFd, version); + fputs("bin ", stream); + fputs("sock ", stream); + if (aPtr->ipc.socket.addrDomain == AF_UNIX) + fprintf(stream, "dom=unix port=%s", aPtr->ipc.socket.name); + else if (aPtr->ipc.socket.addrDomain == AF_INET) { + if (aPtr->ipc.socket.name) + fprintf(stream, "dom=inet port=%s (%d)", + aPtr->ipc.socket.name, aPtr->ipc.socket.port); + else + fprintf(stream, "dom=inet port=%d", aPtr->ipc.socket.port); + } + else if (aPtr->ipc.socket.addrDomain == AF_INET6) { + if (aPtr->ipc.socket.name) + fprintf(stream, "dom=ipv6 port=%s (%d)", + aPtr->ipc.socket.name, aPtr->ipc.socket.port); + else + fprintf(stream, "dom=ipv6 port=%d", aPtr->ipc.socket.port); + } + else { + fputs("dom=???", stream); + } + if (aPtr->ipc.socket.commandLine) { + fputs(" cmd=", stream); + fputs(aPtr->ipc.socket.commandLine, stream); + } + putc('\n', stream); + break; + + case AGENT_PIPE: + version = __pmVersionIPC(aPtr->inFd); + fprintf(stream, " %3d %5" FMT_PID " %3d %3d %3d ", + aPtr->pmDomainId, aPtr->ipc.pipe.agentPid, aPtr->inFd, aPtr->outFd, version); + fputs("bin ", stream); + if (aPtr->ipc.pipe.commandLine) { + fputs("pipe cmd=", stream); + fputs(aPtr->ipc.pipe.commandLine, stream); + putc('\n', stream); + } + break; + + default: + fputs("????\n", stream); + break; + } + } + fflush(stream); /* Ensure that it appears now */ +} + +/* Load the DSO for a specified agent and initialise it. */ +static int +GetAgentDso(AgentInfo *aPtr) +{ + DsoInfo *dso = &aPtr->ipc.dso; + const char *name; + unsigned int challenge; + + aPtr->status.connected = 0; + aPtr->reason = REASON_NOSTART; + + name = __pmFindPMDA(dso->pathName); + if (name == NULL) { + fprintf(stderr, "Cannot find %s DSO at \"%s\"\n", + aPtr->pmDomainLabel, dso->pathName); + fputc('\n', stderr); + return -1; + } + + if (name != dso->pathName) { + /* some searching was done */ + free(dso->pathName); + dso->pathName = strdup(name); + if (dso->pathName == NULL) { + __pmNoMem("pmcd config: pathName", strlen(name), PM_FATAL_ERR); + } + dso->xlatePath = 1; + } + +#if defined(HAVE_DLOPEN) + /* + * RTLD_NOW would be better in terms of detecting unresolved symbols + * now, rather than taking a SEGV later ... but various combinations + * of dynamic and static libraries used to create the DSO PMDA, + * combined with hiding symbols in the DSO PMDA may result in benign + * unresolved symbols remaining and the dlopen() would fail under + * these circumstances. + */ + dso->dlHandle = dlopen(dso->pathName, RTLD_LAZY); +#else + fprintf(stderr, "Error attaching %s DSO at \"%s\"\n", + aPtr->pmDomainLabel, dso->pathName); + fprintf(stderr, "No dynamic shared library support on this platform\n"); + return -1; +#endif + + if (dso->dlHandle == NULL) { + fprintf(stderr, "Error attaching %s DSO at \"%s\"\n", + aPtr->pmDomainLabel, dso->pathName); +#if defined(HAVE_DLOPEN) + fprintf(stderr, "%s\n\n", dlerror()); +#else + fprintf(stderr, "%s\n\n", osstrerror()); +#endif + return -1; + } + + /* Get a pointer to the DSO's init function and call it to get the agent's + dispatch table for the DSO. */ + +#if defined(HAVE_DLOPEN) + dso->initFn = (void (*)(pmdaInterface*))dlsym(dso->dlHandle, dso->entryPoint); + if (dso->initFn == NULL) { + fprintf(stderr, "Couldn't find init function `%s' in %s DSO\n", + dso->entryPoint, aPtr->pmDomainLabel); + dlclose(dso->dlHandle); + return -1; + } +#endif + + /* + * Pass in the expected domain id. + * The PMDA initialization routine can (a) ignore it, (b) check it + * is the expected value, or (c) self-adapt. + */ + dso->dispatch.domain = aPtr->pmDomainId; + + /* + * the PMDA interface / PMAPI version discovery as a "challenge" ... + * for pmda_interface it is all the bits being set, + * for pmapi_version it is the complement of the one you are using now + */ + challenge = 0xff; + dso->dispatch.comm.pmda_interface = challenge; + dso->dispatch.comm.pmapi_version = ~PMAPI_VERSION; + + dso->dispatch.comm.flags = 0; + dso->dispatch.status = 0; + + (*dso->initFn)(&dso->dispatch); + + if (dso->dispatch.status != 0) { + /* initialization failed for some reason */ + fprintf(stderr, + "Initialization routine %s in %s DSO failed: %s\n", + dso->entryPoint, aPtr->pmDomainLabel, + pmErrStr(dso->dispatch.status)); +#if defined(HAVE_DLOPEN) + dlclose(dso->dlHandle); +#endif + return -1; + } + + if (dso->dispatch.comm.pmda_interface < PMDA_INTERFACE_2 || + dso->dispatch.comm.pmda_interface > PMDA_INTERFACE_LATEST) { + __pmNotifyErr(LOG_ERR, + "Unknown PMDA interface version (%d) used by DSO %s\n", + dso->dispatch.comm.pmda_interface, aPtr->pmDomainLabel); +#if defined(HAVE_DLOPEN) + dlclose(dso->dlHandle); +#endif + return -1; + } + + if (dso->dispatch.comm.pmapi_version == PMAPI_VERSION_2) + aPtr->pduVersion = PDU_VERSION2; + else { + __pmNotifyErr(LOG_ERR, + "Unsupported PMAPI version (%d) used by DSO %s\n", + dso->dispatch.comm.pmapi_version, aPtr->pmDomainLabel); +#if defined(HAVE_DLOPEN) + dlclose(dso->dlHandle); +#endif + return -1; + } + + aPtr->reason = 0; + aPtr->status.connected = 1; + aPtr->status.flags = dso->dispatch.comm.flags; + if (dso->dispatch.comm.flags & PDU_FLAG_AUTH) + ClientsAuthentication(aPtr); + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) + fprintf(stderr, "pmcd: started DSO PMDA %s (%d) using pmPMDA version=%d, " + "PDU version=%d\n", aPtr->pmDomainLabel, aPtr->pmDomainId, + dso->dispatch.comm.pmda_interface, aPtr->pduVersion); +#endif + + return 0; +} + + +/* For creating and establishing contact with agents of the PMCD. */ +static void +ContactAgents(void) +{ + int i; + int sts = 0; + int createdSocketAgents = 0; + AgentInfo *aPtr; + + for (i = 0; i < nAgents; i++) { + aPtr = &agent[i]; + if (aPtr->status.connected) + continue; + switch (aPtr->ipcType) { + case AGENT_DSO: + sts = GetAgentDso(aPtr); + break; + + case AGENT_SOCKET: + if (aPtr->ipc.socket.argv) { /* Create agent if required */ + sts = CreateAgent(aPtr); + if (sts >= 0) + createdSocketAgents = 1; + + /* Don't attempt to connect yet, if the agent has just been + created, it will need time to initialise socket. */ + } + else + sts = ConnectSocketAgent(aPtr); + break; /* Connect to existing agent */ + + case AGENT_PIPE: + sts = CreateAgent(aPtr); + break; + } + aPtr->status.connected = sts == 0; + if (aPtr->status.connected) { + if (aPtr->ipcType == AGENT_DSO) + pmcd_trace(TR_ADD_AGENT, aPtr->pmDomainId, -1, -1); + else + pmcd_trace(TR_ADD_AGENT, aPtr->pmDomainId, aPtr->inFd, aPtr->outFd); + MarkStateChanges(PMCD_ADD_AGENT); + aPtr->status.notReady = aPtr->status.startNotReady; + } + else + aPtr->reason = REASON_NOSTART; + } + + /* Allow newly created socket agents time to initialise before attempting + to connect to them. */ + + if (createdSocketAgents) { + sleep(2); /* Allow 2 second for startup */ + for (i = 0; i < nAgents; i++) { + aPtr = &agent[i]; + if (aPtr->ipcType == AGENT_SOCKET && + aPtr->ipc.socket.agentPid != (pid_t)-1) { + sts = ConnectSocketAgent(aPtr); + aPtr->status.connected = sts == 0; + if (!aPtr->status.connected) + aPtr->reason = REASON_NOSTART; + } + } + } +} + +int +ParseInitAgents(char *fileName) +{ + int sts; + int i; + FILE *configFile; + struct stat statBuf; + static int firstTime = 1; + + memset(&configFileTime, 0, sizeof(configFileTime)); + configFile = fopen(fileName, "r"); + if (configFile == NULL) + fprintf(stderr, "ParseInitAgents: %s: %s\n", fileName, osstrerror()); + else if (stat(fileName, &statBuf) == -1) + fprintf(stderr, "ParseInitAgents: stat(%s): %s\n", + fileName, osstrerror()); + else { +#if defined(HAVE_ST_MTIME_WITH_E) && defined(HAVE_STAT_TIME_T) + configFileTime = statBuf.st_mtime; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) + fprintf(stderr, "ParseInitAgents: configFileTime=%ld sec\n", + (long)configFileTime); +#endif +#elif defined(HAVE_ST_MTIME_WITH_SPEC) + configFileTime = statBuf.st_mtimespec; /* struct assignment */ +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) + fprintf(stderr, "ParseInitAgents: configFileTime=%ld.%09ld sec\n", + (long)configFileTime.tv_sec, (long)configFileTime.tv_nsec); +#endif +#elif defined(HAVE_STAT_TIMESTRUC) || defined(HAVE_STAT_TIMESPEC) || defined(HAVE_STAT_TIMESPEC_T) + configFileTime = statBuf.st_mtim; /* struct assignment */ +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) + fprintf(stderr, "ParseInitAgents: configFileTime=%ld.%09ld sec\n", + (long)configFileTime.tv_sec, (long)configFileTime.tv_nsec); +#endif +#else +!bozo! +#endif + } + if (configFile == NULL) + return -1; + + if (firstTime) + if (__pmAccAddOp(PMCD_OP_FETCH) < 0 || __pmAccAddOp(PMCD_OP_STORE) < 0) { + fprintf(stderr, + "ParseInitAgents: __pmAccAddOp: can't create access ops\n"); + exit(1); + } + + sts = ReadConfigFile(configFile); + fclose(configFile); + + /* If pmcd is restarting, don't create/contact the agents until the results + * of the parse can be compared with the previous setup to determine + * whether anything has changed. + */ + if (!firstTime) + return sts; + + firstTime = 0; + if (sts == 0) { + ContactAgents(); + for (i = 0; i < MAXDOMID + 2; i++) + mapdom[i] = nAgents; + for (i = 0; i < nAgents; i++) + if (agent[i].status.connected) + mapdom[agent[i].pmDomainId] = i; + } + return sts; +} + +static int +AgentsDiffer(AgentInfo *a1, AgentInfo *a2) +{ + int i; + + if (a1->pmDomainId != a2->pmDomainId) + return 1; + if (a1->ipcType != a2->ipcType) + return 1; + if (a1->ipcType == AGENT_DSO) { + DsoInfo *dso1 = &a1->ipc.dso; + DsoInfo *dso2 = &a2->ipc.dso; + if (strcmp(dso1->pathName, dso2->pathName) != 0) + return 1; + if (dso1->entryPoint == NULL || dso2->entryPoint == NULL) + return 1; /* should never happen */ + if (strcmp(dso1->entryPoint, dso2->entryPoint)) + return 1; + } + else if (a1->ipcType == AGENT_SOCKET) { + SocketInfo *sock1 = &a1->ipc.socket; + SocketInfo *sock2 = &a2->ipc.socket; + + if (sock1 == NULL || sock2 == NULL) + return 1; /* should never happen */ + if (sock1->addrDomain != sock2->addrDomain) + return 1; + /* The names don't really matter, it's the port that counts */ + if (sock1->port != sock2->port) + return 1; + if ((sock1->commandLine == NULL && sock2->commandLine != NULL) || + (sock1->commandLine != NULL && sock2->commandLine == NULL)) + return 1; + if (sock1->argv != NULL && sock2->argv != NULL) { + /* Don't just compare commandLines, changes may be cosmetic */ + for (i = 0; sock1->argv[i] != NULL && sock2->argv[i] != NULL; i++) + if (strcmp(sock1->argv[i], sock2->argv[i])) + return 1; + if (sock1->argv[i] != NULL || sock2->argv[i] != NULL) + return 1; + } + else if ((sock1->argv == NULL && sock2->argv != NULL) || + (sock1->argv != NULL && sock2->argv == NULL)) + return 1; + } + + else { + PipeInfo *pipe1 = &a1->ipc.pipe; + PipeInfo *pipe2 = &a2->ipc.pipe; + + if (pipe1 == NULL || pipe2 == NULL) + return 1; /* should never happen */ + if ((pipe1->commandLine == NULL && pipe2->commandLine != NULL) || + (pipe1->commandLine != NULL && pipe2->commandLine == NULL)) + return 1; + if (pipe1->argv != NULL && pipe2->argv != NULL) { + /* Don't just compare commandLines, changes may be cosmetic */ + for (i = 0; pipe1->argv[i] != NULL && pipe2->argv[i] != NULL; i++) + if (strcmp(pipe1->argv[i], pipe2->argv[i])) + return 1; + if (pipe1->argv[i] != NULL || pipe2->argv[i] != NULL) + return 1; + } + else if ((pipe1->argv == NULL && pipe2->argv != NULL) || + (pipe1->argv != NULL && pipe2->argv == NULL)) + return 1; + } + return 0; +} + +/* Make the "dest" agent the equivalent of an existing "src" agent. + * This assumes that the agents are identical according to AgentsDiffer(), and + * that they have distinct copies of the fields compared therein. + * Note that only the the low level PDU I/O information is copied here. + */ +static void +DupAgent(AgentInfo *dest, AgentInfo *src) +{ + dest->inFd = src->inFd; + dest->outFd = src->outFd; + dest->profClient = src->profClient; + dest->profIndex = src->profIndex; + /* IMPORTANT: copy the status, connections stay connected */ + memcpy(&dest->status, &src->status, sizeof(dest->status)); + if (src->ipcType == AGENT_DSO) { + dest->ipc.dso.dlHandle = src->ipc.dso.dlHandle; + memcpy(&dest->ipc.dso.dispatch, &src->ipc.dso.dispatch, + sizeof(dest->ipc.dso.dispatch)); + /* initFn should never be needed */ + dest->ipc.dso.initFn = (DsoInitPtr)0; + } + else if (src->ipcType == AGENT_SOCKET) + dest->ipc.socket.agentPid = src->ipc.socket.agentPid; + else + dest->ipc.pipe.agentPid = src->ipc.pipe.agentPid; +} + +void +ParseRestartAgents(char *fileName) +{ + int sts; + int i, j; + struct stat statBuf; + AgentInfo *oldAgent; + int oldNAgents; + AgentInfo *ap; + __pmFdSet fds; + + /* Clean up any deceased agents. We haven't seen an agent's death unless + * a PDU transfer involving the agent has occurred. This cleans up others + * as well. + */ + __pmFD_ZERO(&fds); + j = -1; + for (i = 0; i < nAgents; i++) { + ap = &agent[i]; + if (ap->status.connected && + (ap->ipcType == AGENT_SOCKET || ap->ipcType == AGENT_PIPE)) { + + __pmFD_SET(ap->outFd, &fds); + if (ap->outFd > j) + j = ap->outFd; + } + } + if (++j) { + /* any agent with output ready has either closed the file descriptor or + * sent an unsolicited PDU. Clean up the agent in either case. + */ + struct timeval timeout = {0, 0}; + + sts = __pmSelectRead(j, &fds, &timeout); + if (sts > 0) { + for (i = 0; i < nAgents; i++) { + ap = &agent[i]; + if (ap->status.connected && + (ap->ipcType == AGENT_SOCKET || ap->ipcType == AGENT_PIPE) && + __pmFD_ISSET(ap->outFd, &fds)) { + + /* try to discover more ... */ + __pmPDU *pb; + sts = __pmGetPDU(ap->outFd, ANY_SIZE, TIMEOUT_NEVER, &pb); + if (sts > 0) + pmcd_trace(TR_RECV_PDU, ap->outFd, sts, (int)((__psint_t)pb & 0xffffffff)); + if (sts == 0) + pmcd_trace(TR_EOF, ap->outFd, -1, -1); + else { + pmcd_trace(TR_WRONG_PDU, ap->outFd, -1, sts); + if (sts > 0) + __pmUnpinPDUBuf(pb); + } + + CleanupAgent(ap, AT_COMM, ap->outFd); + } + } + } + else if (sts < 0) + fprintf(stderr, "pmcd: deceased agents select: %s\n", + netstrerror()); + } + + /* gather any deceased children */ + HarvestAgents(0); + + if (stat(fileName, &statBuf) == -1) { + fprintf(stderr, "ParseRestartAgents: stat(%s): %s\n", + fileName, osstrerror()); + fprintf(stderr, "Configuration left unchanged\n"); + return; + } + + /* If the config file's modification time hasn't changed, just try to + * restart any deceased agents + */ +#if defined(HAVE_ST_MTIME_WITH_SPEC) +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) + fprintf(stderr, "ParseRestartAgents: new configFileTime=%ld.%09ld sec\n", + (long)statBuf.st_mtimespec.tv_sec, (long)statBuf.st_mtimespec.tv_nsec); +#endif + if (statBuf.st_mtimespec.tv_sec == configFileTime.tv_sec && + statBuf.st_mtimespec.tv_nsec == configFileTime.tv_nsec) { +#elif defined(HAVE_STAT_TIMESPEC_T) || defined(HAVE_STAT_TIMESTRUC) || defined(HAVE_STAT_TIMESPEC) +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) + fprintf(stderr, "ParseRestartAgents: new configFileTime=%ld.%09ld sec\n", + (long)statBuf.st_mtim.tv_sec, (long)statBuf.st_mtim.tv_nsec); +#endif + if (statBuf.st_mtim.tv_sec == configFileTime.tv_sec && + statBuf.st_mtim.tv_nsec == configFileTime.tv_nsec) { +#elif defined(HAVE_STAT_TIME_T) +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) + fprintf(stderr, "ParseRestartAgents: new configFileTime=%ld sec\n", + (long)configFileTime); +#endif + if (statBuf.st_mtime == configFileTime) { +#else +!bozo! +#endif + fprintf(stderr, "Configuration file '%s' unchanged\n", fileName); + fprintf(stderr, "Restarting any deceased agents:\n"); + j = 0; + for (i = 0; i < nAgents; i++) + if (!agent[i].status.connected) { + fprintf(stderr, " \"%s\" agent\n", + agent[i].pmDomainLabel); + j++; + } + if (j == 0) + fprintf(stderr, " (no agents required restarting)\n"); + else { + putc('\n', stderr); + ContactAgents(); + for (i = 0; i < nAgents; i++) { + mapdom[agent[i].pmDomainId] = + agent[i].status.connected ? i : nAgents; + } + + MarkStateChanges(PMCD_RESTART_AGENT); + } + PrintAgentInfo(stderr); + __pmAccDumpLists(stderr); + return; + } + + /* Save the current agent[] and host access tables, Reset the internal + * state of the config file parser and re-parse the config file. + */ + oldAgent = agent; + oldNAgents = nAgents; + agent = NULL; + nAgents = 0; + szAgents = 0; + scanInit = 0; + scanError = 0; + if (__pmAccSaveLists() < 0) { + fprintf(stderr, "Error saving access controls\n"); + sts = -2; + } + else + sts = ParseInitAgents(fileName); + + /* If the config file had errors or there were no valid agents in the new + * config file, ignore it and stick with the old setup. + */ + if (sts < 0 || nAgents == 0) { + if (sts == -1) + fprintf(stderr, + "Configuration file '%s' has errors\n", fileName); + else + fprintf(stderr, + "Configuration file '%s' has no valid agents\n", + fileName); + fprintf(stderr, "Configuration left unchanged\n"); + agent = oldAgent; + nAgents = oldNAgents; + if (sts != -2 && __pmAccRestoreLists() < 0) { + fprintf(stderr, "Error restoring access controls!\n"); + exit(1); + } + PrintAgentInfo(stderr); + __pmAccDumpLists(stderr); + return; + } + + /* Reconcile the old and new agent tables, creating or destroying agents + * as reqired. + */ + for (j = 0; j < oldNAgents; j++) + oldAgent[j].status.restartKeep = 0; + + for (i = 0; i < nAgents; i++) + for (j = 0; j < oldNAgents; j++) + if (!AgentsDiffer(&agent[i], &oldAgent[j]) && + oldAgent[j].status.connected) { + DupAgent(&agent[i], &oldAgent[j]); + oldAgent[j].status.restartKeep = 1; + } + + for (j = 0; j < oldNAgents; j++) { + if (oldAgent[j].status.connected && !oldAgent[j].status.restartKeep) + CleanupAgent(&oldAgent[j], AT_CONFIG, 0); + FreeAgent(&oldAgent[j]); + } + free(oldAgent); + __pmAccFreeSavedLists(); + + /* Start the new agents */ + ContactAgents(); + for (i = 0; i < MAXDOMID + 2; i++) + mapdom[i] = nAgents; + for (i = 0; i < nAgents; i++) + if (agent[i].status.connected) + mapdom[agent[i].pmDomainId] = i; + + /* Now recalculate the access controls for each client and update the + * connection count in the ACL entries matching the client (and account). + * If the client is no longer permitted the connection because of a change + * in permissions or connection limit, the client's connection is closed. + */ + for (i = 0; i < nClients; i++) { + ClientInfo *cp = &client[i]; + + if (!cp->status.connected) + continue; + if ((sts = CheckClientAccess(cp)) >= 0) + sts = CheckAccountAccess(cp); + if (sts < 0) { + /* ignore errors, the client is being terminated in any case */ + pmcd_trace(TR_XMIT_PDU, cp->fd, PDU_ERROR, sts); + __pmSendError(cp->fd, FROM_ANON, sts); + CleanupClient(cp, sts); + } + } + + PrintAgentInfo(stderr); + __pmAccDumpLists(stderr); + + /* Gather any deceased children, some may be PMDAs that were + * terminated by CleanupAgent or killed and had not exited + * when the previous harvest() was done + */ + HarvestAgents(0); +} diff --git a/src/pmcd/src/dofetch.c b/src/pmcd/src/dofetch.c new file mode 100644 index 0000000..0e73fe2 --- /dev/null +++ b/src/pmcd/src/dofetch.c @@ -0,0 +1,582 @@ +/* + * Copyright (c) 2012-2013 Red Hat. + * Copyright (c) 1995 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. + */ + +#include "pmapi.h" +#include "impl.h" +#include "pmcd.h" + +/* Freq. histogram: pmids for each agent in current fetch request */ + +static int *aFreq; + +/* Routine to break a list of pmIDs up into sublists of metrics within the + * same metric domain. The resulting lists are returned via a pointer to an + * array of per-domain lists as defined by the struct below. Any metrics for + * which no agent exists are collected into a list at the end of the list of + * valid lists. This list has domain = -1 and is used to indicate the end of + * the list of pmID lists. + */ + +typedef struct { + int domain; + int listSize; + pmID *list; +} DomPmidList; + +static DomPmidList * +SplitPmidList(int nPmids, pmID *pmidList) +{ + int i, j; + static int *resIndex = NULL; /* resIndex[k] = index of agent[k]'s list in result */ + static int nDoms = 0; /* No. of entries in two tables above */ + int nGood; + static int currentSize = 0; + int resultSize; + static DomPmidList *result; + pmID *resultPmids; + + /* Allocate the frequency histogram and array for mapping from agent to + * result list index. Because a SIGHUP reconfiguration may have caused a + * change in the number of agents, reallocation using a new size may be + * necessary. + * There are nAgents + 1 entries in the aFreq and resIndex arrays. The + * last entry in each is used for the pmIDs for which no agent could be + * found. + */ + if (nAgents > nDoms) { + nDoms = nAgents; + if (resIndex != NULL) + free(resIndex); + if (aFreq != NULL) + free(aFreq); + resIndex = (int *)malloc((nAgents + 1) * sizeof(int)); + aFreq = (int *)malloc((nAgents + 1) * sizeof(int)); + if (resIndex == NULL || aFreq == NULL) { + __pmNoMem("SplitPmidList.resIndex", 2 * (nAgents + 1) * sizeof(int), PM_FATAL_ERR); + } + } + + memset(aFreq, 0, (nAgents + 1) * sizeof(aFreq[0])); + + if (nPmids == 1) { + /* FastTrack this case */ + for (i = 0; i < nAgents; i++) + resIndex[i] = 1; + i = mapdom[((__pmID_int *)&pmidList[0])->domain]; + aFreq[i] = 1; + resIndex[i] = 0; + nGood = i == nAgents ? 0 : 1; + goto doit; + } + + /* + * Build a frequency histogram of metric domains (use aFreq[nAgents], + * via mapdom[] for pmids for which there is no agent). + */ + for (i = 0; i < nPmids; i++) { + j = mapdom[((__pmID_int *)&pmidList[i])->domain]; + aFreq[j]++; + } + + /* Build the mapping between agent index and the position of the agent's + * subset of the pmidList in the returned result's DomPmidList. + */ + nGood = 0; + for (i = 0; i < nAgents; i++) + if (aFreq[i]) + nGood++; + + /* nGood is the number of "valid" result pmid lists. It is also the INDEX + * of the BAD list in the resulting list of DomPmidLists). + */ + j = 0; + for (i = 0; i < nAgents; i++) + resIndex[i] = (aFreq[i]) ? j++ : nGood; + resIndex[nAgents] = nGood; /* For the "bad" list */ + + /* Now malloc up a single heap block for the resulting list of pmID lists. + * First is a list of (nDoms + 1) DomPmidLists (the last is a sentinel with + * a domain of -1), then come the pmID lists pointed to by the + * DomPmidLists. + */ +doit: + resultSize = (nGood + 1) * (int)sizeof(DomPmidList); + resultSize += nPmids * sizeof(pmID); + if (resultSize > currentSize) { + if (currentSize > 0) + free(result); + result = (DomPmidList *)malloc(resultSize); + if (result == NULL) { + __pmNoMem("SplitPmidList.result", resultSize, PM_FATAL_ERR); + } + currentSize = resultSize; + } + + resultPmids = (pmID *)&result[nGood + 1]; + if (nPmids == 1) { + /* more FastTrack */ + if (nGood) { + /* domain known, otherwise things fixed up below */ + i = mapdom[((__pmID_int *)&pmidList[0])->domain]; + j = resIndex[i]; + result[j].domain = agent[i].pmDomainId; + result[j].listSize = 0; + result[j].list = resultPmids; + resultPmids++; + } + } + else { + for (i = 0; i < nAgents; i++) { + if (aFreq[i]) { + j = resIndex[i]; + result[j].domain = agent[i].pmDomainId; + result[j].listSize = 0; + result[j].list = resultPmids; + resultPmids += aFreq[i]; + } + } + } + result[nGood].domain = -1; /* Set up the "bad" list */ + result[nGood].listSize = 0; + result[nGood].list = resultPmids; + + for (i = 0; i < nPmids; i++) { + j = resIndex[mapdom[((__pmID_int *)&pmidList[i])->domain]]; + result[j].list[result[j].listSize++] = pmidList[i]; + } + return result; +} + +/* Build a pmResult indicating that no values are available for the pmID list + * supplied. + */ + +static pmResult * +MakeBadResult(int npmids, pmID *list, int sts) +{ + int need; + int i; + pmValueSet *vSet; + pmResult *result; + + need = (int)sizeof(pmResult) + + (npmids - 1) * (int)sizeof(pmValueSet *); + /* npmids - 1 because there is already 1 pmValueSet* in a pmResult */ + result = (pmResult *)malloc(need); + if (result == NULL) { + __pmNoMem("MakeBadResult.result", need, PM_FATAL_ERR); + } + result->numpmid = npmids; + for (i = 0; i < npmids; i++) { + vSet = (pmValueSet *)malloc(sizeof(pmValueSet)); + if (vSet == NULL) { + __pmNoMem("MakeBadResult.vSet", sizeof(pmValueSet), PM_FATAL_ERR); + } + result->vset[i] = vSet; + vSet->pmid = list[i]; + vSet->numval = sts; + } + return result; +} + +static pmResult * +SendFetch(DomPmidList *dpList, AgentInfo *aPtr, ClientInfo *cPtr, int ctxnum) +{ + pmResult *result = NULL; + int sts = 0; + static __pmTimeval when = {0, 0}; /* Agents never see archive requests */ + int bad = 0; + int i; + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) { + fprintf(stderr, "SendFetch %d metrics to PMDA domain %d ", + dpList->listSize, dpList->domain); + switch (aPtr->ipcType) { + case AGENT_DSO: + fprintf(stderr, "(dso)\n"); + break; + + case AGENT_SOCKET: + fprintf(stderr, "(socket)\n"); + break; + + case AGENT_PIPE: + fprintf(stderr, "(pipe)\n"); + break; + + default: + fprintf(stderr, "(type %d unknown!)\n", aPtr->ipcType); + break; + } + for (i = 0; i < dpList->listSize; i++) + fprintf(stderr, " pmid[%d] %s\n", i, pmIDStr(dpList->list[i])); + } +#endif + + /* status.madeDsoResult is only used for DSO agents so don't waste time by + * checking that the agent is a DSO first. + */ + aPtr->status.madeDsoResult = 0; + + if (aPtr->profClient != cPtr || ctxnum != aPtr->profIndex) { + if (aPtr->ipcType == AGENT_DSO) { + if (aPtr->ipc.dso.dispatch.comm.pmda_interface >= PMDA_INTERFACE_5) + aPtr->ipc.dso.dispatch.version.four.ext->e_context = cPtr - client; + sts = aPtr->ipc.dso.dispatch.version.any.profile(cPtr->profile[ctxnum], + aPtr->ipc.dso.dispatch.version.any.ext); + } + else { + if (aPtr->status.notReady == 0) { + pmcd_trace(TR_XMIT_PDU, aPtr->inFd, PDU_PROFILE, ctxnum); + if ((sts = __pmSendProfile(aPtr->inFd, cPtr - client, + ctxnum, cPtr->profile[ctxnum])) < 0) { + pmcd_trace(TR_XMIT_ERR, aPtr->inFd, PDU_PROFILE, sts); + } + } else { + sts = PM_ERR_AGAIN; + } + + } + if (sts >= 0) { + aPtr->profClient = cPtr; + aPtr->profIndex = ctxnum; + } + } + + if (sts >= 0) { + if (aPtr->ipcType == AGENT_DSO) { + if (aPtr->ipc.dso.dispatch.comm.pmda_interface >= PMDA_INTERFACE_5) + aPtr->ipc.dso.dispatch.version.four.ext->e_context = cPtr - client; + sts = aPtr->ipc.dso.dispatch.version.any.fetch(dpList->listSize, + dpList->list, &result, + aPtr->ipc.dso.dispatch.version.any.ext); + if (sts >= 0) { + if (result == NULL) { + __pmNotifyErr(LOG_WARNING, + "\"%s\" agent (DSO) returned a null result\n", + aPtr->pmDomainLabel); + sts = PM_ERR_PMID; + bad = 1; + } + else { + if (result->numpmid != dpList->listSize) { + __pmNotifyErr(LOG_WARNING, + "\"%s\" agent (DSO) returned %d pmIDs (%d expected)\n", + aPtr->pmDomainLabel, + result->numpmid,dpList->listSize); + sts = PM_ERR_PMID; + bad = 2; + } + } + } + } + else { + if (aPtr->status.notReady == 0) { + /* agent is ready for PDUs */ + pmcd_trace(TR_XMIT_PDU, aPtr->inFd, PDU_FETCH, dpList->listSize); + if ((sts = __pmSendFetch(aPtr->inFd, cPtr - client, ctxnum, &when, + dpList->listSize, dpList->list)) < 0) + pmcd_trace(TR_XMIT_ERR, aPtr->inFd, PDU_FETCH, sts); + } + else { + /* agent is not ready for PDUs */ + sts = PM_ERR_AGAIN; + } + } + } + + if (sts < 0) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) + switch (bad) { + case 0: + fprintf(stderr, "FETCH error: \"%s\" agent : %s\n", + aPtr->pmDomainLabel, pmErrStr(sts)); + break; + case 1: + fprintf(stderr, "\"%s\" agent (DSO) returned a null result\n", + aPtr->pmDomainLabel); + break; + case 2: + fprintf(stderr, "\"%s\" agent (DSO) returned %d pmIDs (%d expected)\n", + aPtr->pmDomainLabel, + result->numpmid, dpList->listSize); + break; + } +#endif + if (aPtr->ipcType == AGENT_DSO) { + aPtr->status.madeDsoResult = 1; + sts = 0; + } + else if (sts == PM_ERR_IPC || sts == PM_ERR_TIMEOUT || sts == -EPIPE) + CleanupAgent(aPtr, AT_COMM, aPtr->inFd); + + result = MakeBadResult(dpList->listSize, dpList->list, sts); + } + + return result; +} + +int +DoFetch(ClientInfo *cip, __pmPDU* pb) +{ + int i, j; + int sts; + int ctxnum; + __pmTimeval when; + int nPmids; + pmID *pmidList; + static pmResult *endResult = NULL; + static int maxnpmids = 0; /* sizes endResult */ + DomPmidList *dList; /* NOTE: NOT indexed by agent index */ + static int nDoms = 0; + static pmResult **results = NULL; + static int *resIndex = NULL; + __pmFdSet waitFds; + __pmFdSet readyFds; + int nWait; + int maxFd; + struct timeval timeout; + + if (nAgents > nDoms) { + if (results != NULL) + free(results); + if (resIndex != NULL) + free(resIndex); + results = (pmResult **)malloc((nAgents + 1) * sizeof (pmResult *)); + resIndex = (int *)malloc((nAgents + 1) * sizeof(int)); + if (results == NULL || resIndex == NULL) { + __pmNoMem("DoFetch.results", (nAgents + 1) * sizeof (pmResult *) + (nAgents + 1) * sizeof(int), PM_FATAL_ERR); + } + nDoms = nAgents; + } + memset(results, 0, (nAgents + 1) * sizeof(results[0])); + + sts = __pmDecodeFetch(pb, &ctxnum, &when, &nPmids, &pmidList); + if (sts < 0) + return sts; + + /* Check that a profile has been received from the specified context */ + if (ctxnum < 0 || ctxnum >= cip->szProfile || + cip->profile[ctxnum] == NULL) { + __pmUnpinPDUBuf(pb); + if (ctxnum < 0 || ctxnum >= cip->szProfile) + __pmNotifyErr(LOG_ERR, "DoFetch: bad ctxnum=%d\n", ctxnum); + else + __pmNotifyErr(LOG_ERR, "DoFetch: no profile for ctxnum=%d\n", ctxnum); + return PM_ERR_NOPROFILE; + } + + if (nPmids > maxnpmids) { + int need; + if (endResult != NULL) + free(endResult); + need = (int)sizeof(pmResult) + (nPmids - 1) * (int)sizeof(pmValueSet *); + if ((endResult = (pmResult *)malloc(need)) == NULL) { + __pmNoMem("DoFetch.endResult", need, PM_FATAL_ERR); + } + maxnpmids = nPmids; + } + + dList = SplitPmidList(nPmids, pmidList); + + /* For each domain in the split pmidList, dispatch the per-domain subset + * of pmIDs to the appropriate agent. For DSO agents, the pmResult will + * come back immediately. If a request cannot be sent to an agent, a + * suitable pmResult (containing metric not available values) will be + * returned. + */ + __pmFD_ZERO(&waitFds); + nWait = 0; + maxFd = -1; + for (i = 0; dList[i].domain != -1; i++) { + j = mapdom[dList[i].domain]; + results[j] = SendFetch(&dList[i], &agent[j], cip, ctxnum); + if (results[j] == NULL) { /* Wait for agent's response */ + int fd = agent[j].outFd; + agent[j].status.busy = 1; + __pmFD_SET(fd, &waitFds); + if (fd > maxFd) + maxFd = fd; + nWait++; + } + } + /* Construct pmResult for bad-pmID list */ + if (dList[i].listSize != 0) + results[nAgents] = MakeBadResult(dList[i].listSize, dList[i].list, PM_ERR_NOAGENT); + + /* Wait for results to roll in from agents */ + while (nWait > 0) { + __pmFD_COPY(&readyFds, &waitFds); + if (nWait > 1) { + timeout.tv_sec = _pmcd_timeout; + timeout.tv_usec = 0; + + sts = __pmSelectRead(maxFd+1, &readyFds, &timeout); + + if (sts == 0) { + __pmNotifyErr(LOG_INFO, "DoFetch: select timeout"); + + /* Timeout, terminate agents with undelivered results */ + for (i = 0; i < nAgents; i++) { + if (agent[i].status.busy) { + /* Find entry in dList for this agent */ + for (j = 0; dList[j].domain != -1; j++) + if (dList[j].domain == agent[i].pmDomainId) + break; + results[i] = MakeBadResult(dList[j].listSize, + dList[j].list, + PM_ERR_NOAGENT); + pmcd_trace(TR_RECV_TIMEOUT, agent[i].outFd, PDU_RESULT, 0); + CleanupAgent(&agent[i], AT_COMM, agent[i].inFd); + } + } + break; + } + else if (sts < 0) { + /* this is not expected to happen! */ + __pmNotifyErr(LOG_ERR, "DoFetch: fatal select failure: %s\n", + netstrerror()); + Shutdown(); + exit(1); + } + } + + /* Read results from agents that have them ready */ + for (i = 0; i < nAgents; i++) { + AgentInfo *ap = &agent[i]; + int pinpdu; + if (!ap->status.busy || !__pmFD_ISSET(ap->outFd, &readyFds)) + continue; + ap->status.busy = 0; + __pmFD_CLR(ap->outFd, &waitFds); + nWait--; + pinpdu = sts = __pmGetPDU(ap->outFd, ANY_SIZE, _pmcd_timeout, &pb); + if (sts > 0) + pmcd_trace(TR_RECV_PDU, ap->outFd, sts, (int)((__psint_t)pb & 0xffffffff)); + if (sts == PDU_RESULT) { + if ((sts = __pmDecodeResult(pb, &results[i])) >= 0) + if (results[i]->numpmid != aFreq[i]) { + pmFreeResult(results[i]); + sts = PM_ERR_IPC; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) + __pmNotifyErr(LOG_ERR, "DoFetch: \"%s\" agent given %d pmIDs, returned %d\n", + ap->pmDomainLabel, aFreq[i], results[i]->numpmid); +#endif + } + } + else { + if (sts == PDU_ERROR) { + int s; + if ((s = __pmDecodeError(pb, &sts)) < 0) + sts = s; + else if (sts >= 0) + sts = PM_ERR_GENERIC; + pmcd_trace(TR_RECV_ERR, ap->outFd, PDU_RESULT, sts); + } + else if (sts >= 0) { + pmcd_trace(TR_WRONG_PDU, ap->outFd, PDU_RESULT, sts); + sts = PM_ERR_IPC; + } + } + if (pinpdu > 0) + __pmUnpinPDUBuf(pb); + + if (sts < 0) { + /* Find entry in dList for this agent */ + for (j = 0; dList[j].domain != -1; j++) + if (dList[j].domain == agent[i].pmDomainId) + break; + results[i] = MakeBadResult(dList[j].listSize, + dList[j].list, sts); + + if (sts == PM_ERR_PMDANOTREADY) { + /* the agent is indicating it can't handle PDUs for now */ + int k; + extern int CheckError(AgentInfo *ap, int sts); + + for (k = 0; k < dList[j].listSize; k++) + results[i]->vset[k]->numval = PM_ERR_AGAIN; + sts = CheckError(&agent[i], sts); + } + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) { + fprintf(stderr, "RESULT error from \"%s\" agent : %s\n", + ap->pmDomainLabel, pmErrStr(sts)); + } +#endif + if (sts == PM_ERR_IPC || sts == PM_ERR_TIMEOUT) + CleanupAgent(ap, AT_COMM, ap->outFd); + } + } + } + + endResult->numpmid = nPmids; + __pmtimevalNow(&endResult->timestamp); + /* The order of the pmIDs in the per-domain results is the same as in the + * original request, but on a per-domain basis. resIndex is an array of + * indeces (one per agent) of the next metric to be retrieved from each + * per-domain result's vset. + */ + memset(resIndex, 0, (nAgents + 1) * sizeof(resIndex[0])); + + for (i = 0; i < nPmids; i++) { + j = mapdom[((__pmID_int *)&pmidList[i])->domain]; + endResult->vset[i] = results[j]->vset[resIndex[j]++]; + } + pmcd_trace(TR_XMIT_PDU, cip->fd, PDU_RESULT, endResult->numpmid); + + sts = 0; + if (cip->status.changes) { + /* notify client of PMCD state change */ + sts = __pmSendError(cip->fd, FROM_ANON, (int)cip->status.changes); + if (sts > 0) + sts = 0; + cip->status.changes = 0; + } + if (sts == 0) + sts = __pmSendResult(cip->fd, FROM_ANON, endResult); + + if (sts < 0) { + pmcd_trace(TR_XMIT_ERR, cip->fd, PDU_RESULT, sts); + CleanupClient(cip, sts); + } + + /* + * pmFreeResult() all the accumulated results. + */ + for (i = 0; dList[i].domain != -1; i++) { + j = mapdom[dList[i].domain]; + if (agent[j].ipcType == AGENT_DSO && agent[j].status.connected && + !agent[j].status.madeDsoResult) + /* Living DSO's manage their own pmResult skeleton unless + * MakeBadResult was called to create the result. The value sets + * within the skeleton need to be freed though! + */ + __pmFreeResultValues(results[j]); + else + /* For others it is dynamically allocated in __pmDecodeResult or + * MakeBadResult + */ + pmFreeResult(results[j]); + } + if (results[nAgents] != NULL) + pmFreeResult(results[nAgents]); + __pmUnpinPDUBuf(pmidList); + return 0; +} diff --git a/src/pmcd/src/dopdus.c b/src/pmcd/src/dopdus.c new file mode 100644 index 0000000..7eb71c4 --- /dev/null +++ b/src/pmcd/src/dopdus.c @@ -0,0 +1,1057 @@ +/* + * Copyright (c) 2012-2013 Red Hat. + * Copyright (c) 1995-2002 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. + */ + +#include "pmcd.h" + +/* Check returned error from a client. + * If client returns ready/not_ready status change, check then update agent + * status. + * If the client goes from not_ready to ready, it sends an unsolicited error + * PDU. If this happens, the retry flag indicates that the expected response + * is yet to arrive, and that the caller should try reading + * and the expected response will follow it. + */ +int +CheckError(AgentInfo *ap, int sts) +{ + int retSts; + + if (sts == PM_ERR_PMDANOTREADY) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) + __pmNotifyErr(LOG_INFO, "%s agent (%s) sent NOT READY\n", + ap->pmDomainLabel, + ap->status.notReady ? "not ready" : "ready"); +#endif + if (ap->status.notReady == 0) { + ap->status.notReady = 1; + retSts = PM_ERR_AGAIN; + } + else + retSts = PM_ERR_IPC; + } + else if (sts == PM_ERR_PMDAREADY) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_APPL0) + __pmNotifyErr(LOG_INFO, "%s agent (%s) sent unexpected READY\n", + ap->pmDomainLabel, + ap->status.notReady ? "not ready" : "ready"); +#endif + retSts = PM_ERR_IPC; + } + else + retSts = sts; + + return retSts; +} + +int +DoText(ClientInfo *cp, __pmPDU* pb) +{ + int sts, s; + int ident; + int type; + AgentInfo *ap; + char *buffer = NULL; + + if ((sts = __pmDecodeTextReq(pb, &ident, &type)) < 0) + return sts; + + if ((ap = FindDomainAgent(((__pmID_int *)&ident)->domain)) == NULL) + return PM_ERR_PMID; + else if (!ap->status.connected) + return PM_ERR_NOAGENT; + + if (ap->ipcType == AGENT_DSO) { + if (ap->ipc.dso.dispatch.comm.pmda_interface >= PMDA_INTERFACE_5) + ap->ipc.dso.dispatch.version.four.ext->e_context = cp - client; + sts = ap->ipc.dso.dispatch.version.any.text(ident, type, &buffer, + ap->ipc.dso.dispatch.version.any.ext); + } + else { + if (ap->status.notReady) + return PM_ERR_AGAIN; + pmcd_trace(TR_XMIT_PDU, ap->inFd, PDU_TEXT_REQ, ident); + sts = __pmSendTextReq(ap->inFd, cp - client, ident, type); + if (sts >= 0) { + int pinpdu; + pinpdu = sts = __pmGetPDU(ap->outFd, ANY_SIZE, _pmcd_timeout, &pb); + if (sts > 0) + pmcd_trace(TR_RECV_PDU, ap->outFd, sts, (int)((__psint_t)pb & 0xffffffff)); + if (sts == PDU_TEXT) + sts = __pmDecodeText(pb, &ident, &buffer); + else if (sts == PDU_ERROR) { + s = __pmDecodeError(pb, &sts); + if (s < 0) + sts = s; + else + sts = CheckError(ap, sts); + pmcd_trace(TR_RECV_ERR, ap->outFd, PDU_TEXT, sts); + } + else { + pmcd_trace(TR_WRONG_PDU, ap->outFd, PDU_TEXT, sts); + sts = PM_ERR_IPC; /* Wrong PDU type */ + } + if (pinpdu > 0) + __pmUnpinPDUBuf(pb); + } + else + pmcd_trace(TR_XMIT_ERR, ap->inFd, PDU_TEXT_REQ, sts); + } + + if (ap->ipcType != AGENT_DSO && + (sts == PM_ERR_IPC || sts == PM_ERR_TIMEOUT || sts == -EPIPE)) + CleanupAgent(ap, AT_COMM, ap->inFd); + + if (sts >= 0) { + pmcd_trace(TR_XMIT_PDU, cp->fd, PDU_TEXT, ident); + sts = __pmSendText(cp->fd, FROM_ANON, ident, buffer); + if (sts < 0 && ap->ipcType != AGENT_DSO) { + pmcd_trace(TR_XMIT_ERR, cp->fd, PDU_TEXT, sts); + CleanupClient(cp, sts); + } + if (ap->ipcType != AGENT_DSO) { + /* daemon PMDAs have a malloc'd buffer */ + free(buffer); + } + } + return sts; +} + +int +DoProfile(ClientInfo *cp, __pmPDU *pb) +{ + __pmProfile *newProf; + int ctxnum, sts, i; + + sts = __pmDecodeProfile(pb, &ctxnum, &newProf); + if (sts >= 0) { + /* Allocate more profile pointers if required */ + if (ctxnum >= cp->szProfile) { + __pmProfile **newProfPtrs; + int need, oldSize = cp->szProfile; + + if (ctxnum - cp->szProfile < 4) + cp->szProfile += 4; + else + cp->szProfile = ctxnum + 1; + need = cp->szProfile * (int)sizeof(__pmProfile *); + if ((newProfPtrs = (__pmProfile **)malloc(need)) == NULL) { + cp->szProfile = oldSize; + __pmNoMem("DoProfile.newProfPtrs", need, PM_RECOV_ERR); + __pmFreeProfile(newProf); + return -oserror(); + } + + /* Copy any old pointers and zero the newly allocated ones */ + if ((need = oldSize * (int)sizeof(__pmProfile *))) { + memcpy(newProfPtrs, cp->profile, need); + free(cp->profile); /* But not the __pmProfile ptrs! */ + } + need = (cp->szProfile - oldSize) * (int)sizeof(__pmProfile *); + memset(&newProfPtrs[oldSize], 0, need); + cp->profile = newProfPtrs; + } + else /* cp->profile is big enough */ + if (cp->profile[ctxnum] != NULL) + __pmFreeProfile(cp->profile[ctxnum]); + cp->profile[ctxnum] = newProf; + + /* "Invalidate" any references to the client context's profile in the + * agents to which the old profile was last sent + */ + for (i = 0; i < nAgents; i++) { + AgentInfo *ap = &agent[i]; + + if (ap->profClient == cp && ap->profIndex == ctxnum) + ap->profClient = NULL; + } + } + return sts; +} + +int +DoDesc(ClientInfo *cp, __pmPDU *pb) +{ + int sts, s; + pmID pmid; + AgentInfo *ap; + pmDesc desc = {0}; + int fdfail = -1; + + if ((sts = __pmDecodeDescReq(pb, &pmid)) < 0) + return sts; + + if ((ap = FindDomainAgent(((__pmID_int *)&pmid)->domain)) == NULL) + return PM_ERR_PMID; + else if (!ap->status.connected) + return PM_ERR_NOAGENT; + + if (ap->ipcType == AGENT_DSO) { + if (ap->ipc.dso.dispatch.comm.pmda_interface >= PMDA_INTERFACE_5) + ap->ipc.dso.dispatch.version.four.ext->e_context = cp - client; + sts = ap->ipc.dso.dispatch.version.any.desc(pmid, &desc, + ap->ipc.dso.dispatch.version.any.ext); + } + else { + if (ap->status.notReady) + return PM_ERR_AGAIN; + pmcd_trace(TR_XMIT_PDU, ap->inFd, PDU_DESC_REQ, (int)pmid); + sts = __pmSendDescReq(ap->inFd, cp - client, pmid); + if (sts >= 0) { + int pinpdu; + pinpdu = sts = __pmGetPDU(ap->outFd, ANY_SIZE, _pmcd_timeout, &pb); + if (sts > 0) + pmcd_trace(TR_RECV_PDU, ap->outFd, sts, (int)((__psint_t)pb & 0xffffffff)); + if (sts == PDU_DESC) + sts = __pmDecodeDesc(pb, &desc); + else if (sts == PDU_ERROR) { + s = __pmDecodeError(pb, &sts); + if (s < 0) + sts = s; + else + sts = CheckError(ap, sts); + pmcd_trace(TR_RECV_ERR, ap->outFd, PDU_DESC, sts); + } + else { + pmcd_trace(TR_WRONG_PDU, ap->outFd, PDU_DESC, sts); + sts = PM_ERR_IPC; /* Wrong PDU type */ + fdfail = ap->outFd; + } + if (pinpdu > 0) + __pmUnpinPDUBuf(pb); + } + else { + pmcd_trace(TR_XMIT_ERR, ap->inFd, PDU_DESC_REQ, sts); + fdfail = ap->inFd; + } + } + + if (sts >= 0) { + pmcd_trace(TR_XMIT_PDU, cp->fd, PDU_DESC, (int)desc.pmid); + sts = __pmSendDesc(cp->fd, FROM_ANON, &desc); + if (sts < 0) { + pmcd_trace(TR_XMIT_ERR, cp->fd, PDU_DESC, sts); + CleanupClient(cp, sts); + } + } + else + if (ap->ipcType != AGENT_DSO && + (sts == PM_ERR_IPC || sts == PM_ERR_TIMEOUT || sts == -EPIPE) && + fdfail != -1) + CleanupAgent(ap, AT_COMM, fdfail); + + return sts; +} + +int +DoInstance(ClientInfo *cp, __pmPDU* pb) +{ + int sts, s; + __pmTimeval when; + pmInDom indom; + int inst; + char *name; + __pmInResult *inresult = NULL; + AgentInfo *ap; + int fdfail = -1; + + sts = __pmDecodeInstanceReq(pb, &when, &indom, &inst, &name); + if (sts < 0) + return sts; + if (when.tv_sec != 0 || when.tv_usec != 0) { + /* + * we have no idea how to do anything but current, yet! + * + * TODO EXCEPTION PCP 2.0 ... + * this may be left over from the pmvcr days, and can be tossed? + * ... leaving it here is benign + */ + if (name != NULL) free(name); + return PM_ERR_NYI; + } + if ((ap = FindDomainAgent(((__pmInDom_int *)&indom)->domain)) == NULL) { + if (name != NULL) free(name); + return PM_ERR_INDOM; + } + else if (!ap->status.connected) { + if (name != NULL) free(name); + return PM_ERR_NOAGENT; + } + + if (ap->ipcType == AGENT_DSO) { + if (ap->ipc.dso.dispatch.comm.pmda_interface >= PMDA_INTERFACE_5) + ap->ipc.dso.dispatch.version.four.ext->e_context = cp - client; + sts = ap->ipc.dso.dispatch.version.any.instance(indom, inst, name, + &inresult, + ap->ipc.dso.dispatch.version.any.ext); + } + else { + if (ap->status.notReady) { + if (name != NULL) free(name); + return PM_ERR_AGAIN; + } + pmcd_trace(TR_XMIT_PDU, ap->inFd, PDU_INSTANCE_REQ, (int)indom); + sts = __pmSendInstanceReq(ap->inFd, cp - client, &when, indom, inst, name); + if (sts >= 0) { + int pinpdu; + pinpdu = sts = __pmGetPDU(ap->outFd, ANY_SIZE, _pmcd_timeout, &pb); + if (sts > 0) + pmcd_trace(TR_RECV_PDU, ap->outFd, sts, (int)((__psint_t)pb & 0xffffffff)); + if (sts == PDU_INSTANCE) + sts = __pmDecodeInstance(pb, &inresult); + else if (sts == PDU_ERROR) { + inresult = NULL; + s = __pmDecodeError(pb, &sts); + if (s < 0) + sts = s; + else + sts = CheckError(ap, sts); + pmcd_trace(TR_RECV_ERR, ap->outFd, PDU_INSTANCE, sts); + } + else { + pmcd_trace(TR_WRONG_PDU, ap->outFd, PDU_INSTANCE, sts); + sts = PM_ERR_IPC; /* Wrong PDU type */ + fdfail = ap->outFd; + } + if (pinpdu > 0) + __pmUnpinPDUBuf(pb); + } + else { + pmcd_trace(TR_XMIT_ERR, ap->inFd, PDU_INSTANCE_REQ, sts); + fdfail = ap->inFd; + } + } + if (name != NULL) free(name); + + if (sts >= 0) { + pmcd_trace(TR_XMIT_PDU, cp->fd, PDU_INSTANCE, (int)(inresult->indom)); + sts = __pmSendInstance(cp->fd, FROM_ANON, inresult); + if (sts < 0) { + pmcd_trace(TR_XMIT_ERR, cp->fd, PDU_INSTANCE, sts); + CleanupClient(cp, sts); + } + __pmFreeInResult(inresult); + } + else + if (ap->ipcType != AGENT_DSO && + (sts == PM_ERR_IPC || sts == PM_ERR_TIMEOUT || sts == -EPIPE) && + fdfail != -1) + CleanupAgent(ap, AT_COMM, fdfail); + + return sts; +} + +/* + * This handler is for remote versions of pmNameAll or pmNameID. + * Note: only one pmid for the list should be sent. + */ +int +DoPMNSIDs(ClientInfo *cp, __pmPDU *pb) +{ + int sts = 0; + int op_sts = 0; + int numnames = 0; + pmID idlist[1]; + char **namelist = NULL; + AgentInfo *ap = NULL; + int fdfail = -1; + + if ((sts = __pmDecodeIDList(pb, 1, idlist, &op_sts)) < 0) + goto fail; + + if ((sts = pmNameAll(idlist[0], &namelist)) < 0) { + /* + * failure may be a real failure, or could be a metric within a + * dynamic sutree of the PMNS + */ + if ((ap = FindDomainAgent(((__pmID_int *)&idlist[0])->domain)) == NULL) { + sts = PM_ERR_NOAGENT; + goto fail; + } + if (!ap->status.connected) { + sts = PM_ERR_NOAGENT; + goto fail; + } + if (ap->ipcType == AGENT_DSO) { + if (ap->ipc.dso.dispatch.comm.pmda_interface >= PMDA_INTERFACE_5) + ap->ipc.dso.dispatch.version.four.ext->e_context = cp - client; + if (ap->ipc.dso.dispatch.comm.pmda_interface >= PMDA_INTERFACE_4) { + sts = ap->ipc.dso.dispatch.version.four.name(idlist[0], &namelist, + ap->ipc.dso.dispatch.version.four.ext); + } + else { + /* Not PMDA_INTERFACE_4 or later */ + sts = PM_ERR_PMID; + } + } + else { + /* daemon PMDA ... ship request on */ + if (ap->status.notReady) + return PM_ERR_AGAIN; + pmcd_trace(TR_XMIT_PDU, ap->inFd, PDU_PMNS_IDS, 1); + sts = __pmSendIDList(ap->inFd, cp - client, 1, &idlist[0], 0); + if (sts >= 0) { + int pinpdu; + pinpdu = sts = __pmGetPDU(ap->outFd, ANY_SIZE, _pmcd_timeout, &pb); + if (sts > 0) + pmcd_trace(TR_RECV_PDU, ap->outFd, sts, (int)((__psint_t)pb & 0xffffffff)); + if (sts == PDU_PMNS_NAMES) { + sts = __pmDecodeNameList(pb, &numnames, &namelist, NULL); + } + else if (sts == PDU_ERROR) { + __pmDecodeError(pb, &sts); + pmcd_trace(TR_RECV_ERR, ap->outFd, PDU_PMNS_NAMES, sts); + } + else { + pmcd_trace(TR_WRONG_PDU, ap->outFd, PDU_PMNS_NAMES, sts); + sts = PM_ERR_IPC; /* Wrong PDU type */ + fdfail = ap->outFd; + } + if (pinpdu > 0) + __pmUnpinPDUBuf(pb); + } + else { + /* __pmSendIDList failed */ + sts = __pmMapErrno(sts); + fdfail = ap->inFd; + } + } + if (sts < 0) goto fail; + } + + numnames = sts; + + pmcd_trace(TR_XMIT_PDU, cp->fd, PDU_PMNS_NAMES, numnames); + if ((sts = __pmSendNameList(cp->fd, FROM_ANON, numnames, namelist, NULL)) < 0){ + pmcd_trace(TR_XMIT_ERR, cp->fd, PDU_PMNS_NAMES, sts); + CleanupClient(cp, sts); + goto fail; + } + /* fall through OK */ + +fail: + if (ap != NULL && ap->ipcType != AGENT_DSO && + (sts == PM_ERR_IPC || sts == PM_ERR_TIMEOUT || sts == -EPIPE) && + fdfail != -1) + CleanupAgent(ap, AT_COMM, fdfail); + if (namelist) free(namelist); + return sts; +} + +/* + * This handler is for the remote version of pmLookupName. + */ +int +DoPMNSNames(ClientInfo *cp, __pmPDU *pb) +{ + int sts = 0; + int numids = 0; + pmID *idlist = NULL; + char **namelist = NULL; + int i; + AgentInfo *ap = NULL; + + if ((sts = __pmDecodeNameList(pb, &numids, &namelist, NULL)) < 0) + goto done; + + if ((idlist = (pmID *)calloc(numids, sizeof(int))) == NULL) { + sts = -oserror(); + goto done; + } + + sts = pmLookupName(numids, namelist, idlist); + for (i = 0; i < numids; i++) { + if (idlist[i] == PM_ID_NULL) continue; + if (pmid_domain(idlist[i]) == DYNAMIC_PMID && pmid_item(idlist[i]) == 0) { + int lsts; + int domain = pmid_cluster(idlist[i]); + /* + * don't return <domain>.*.* ... all return paths from here + * must either set a valid PMID in idlist[i] or indicate + * the first error in the return from pmLookupName + */ + idlist[i] = PM_ID_NULL; /* default case if cannot translate */ + if ((ap = FindDomainAgent(domain)) == NULL) { + if (sts > 0) sts = PM_ERR_NOAGENT; + continue; + } + if (!ap->status.connected) { + if (sts > 0) sts = PM_ERR_NOAGENT; + continue; + } + if (ap->ipcType == AGENT_DSO) { + if (ap->ipc.dso.dispatch.comm.pmda_interface >= PMDA_INTERFACE_5) + ap->ipc.dso.dispatch.version.four.ext->e_context = cp - client; + if (ap->ipc.dso.dispatch.comm.pmda_interface >= PMDA_INTERFACE_4) { + lsts = ap->ipc.dso.dispatch.version.four.pmid(namelist[i], &idlist[i], + ap->ipc.dso.dispatch.version.four.ext); + } + else { + /* Not PMDA_INTERFACE_4 or later */ + lsts = PM_ERR_NAME; + } + } + else { + /* daemon PMDA ... ship request on */ + int fdfail = -1; + if (ap->status.notReady) + lsts = PM_ERR_AGAIN; + else { + pmcd_trace(TR_XMIT_PDU, ap->inFd, PDU_PMNS_NAMES, 1); + lsts = __pmSendNameList(ap->inFd, cp - client, 1, &namelist[i], NULL); + if (lsts >= 0) { + int pinpdu; + pinpdu = lsts = __pmGetPDU(ap->outFd, ANY_SIZE, _pmcd_timeout, &pb); + if (lsts > 0) + pmcd_trace(TR_RECV_PDU, ap->outFd, sts, (int)((__psint_t)pb & 0xffffffff)); + if (lsts == PDU_PMNS_IDS) { + int xsts; + lsts = __pmDecodeIDList(pb, 1, &idlist[i], &xsts); + if (lsts >= 0) + lsts = xsts; + } + else if (lsts == PDU_ERROR) { + __pmDecodeError(pb, &lsts); + pmcd_trace(TR_RECV_ERR, ap->outFd, PDU_PMNS_IDS, lsts); + } + else { + pmcd_trace(TR_WRONG_PDU, ap->outFd, PDU_PMNS_IDS, sts); + lsts = PM_ERR_IPC; /* Wrong PDU type */ + fdfail = ap->outFd; + } + if (pinpdu > 0) + __pmUnpinPDUBuf(pb); + } + else { + /* __pmSendNameList failed */ + lsts = __pmMapErrno(lsts); + pmcd_trace(TR_XMIT_ERR, ap->inFd, PDU_PMNS_NAMES, lsts); + fdfail = ap->inFd; + } + } + if (ap != NULL && ap->ipcType != AGENT_DSO && + (lsts == PM_ERR_IPC || lsts == PM_ERR_TIMEOUT || lsts == -EPIPE) && + fdfail != -1) + CleanupAgent(ap, AT_COMM, fdfail); + } + /* + * only set error status to the current error status + * if this is the first error for this set of metrics + */ + if (lsts < 0 && sts > 0) sts = lsts; + } + } + + if (sts < 0) { + /* If get an error which should be passed back along + * with valid data to the client + * then do NOT fail -> return status with the id-list. + */ + if (sts != PM_ERR_NAME && sts != PM_ERR_NONLEAF && sts != PM_ERR_NOAGENT) + goto done; + } + + pmcd_trace(TR_XMIT_PDU, cp->fd, PDU_PMNS_IDS, numids); + if ((sts = __pmSendIDList(cp->fd, FROM_ANON, numids, idlist, sts)) < 0) { + pmcd_trace(TR_XMIT_ERR, cp->fd, PDU_PMNS_IDS, sts); + CleanupClient(cp, sts); + goto done; + } + +done: + if (idlist) free(idlist); + if (namelist) free(namelist); + + return sts; +} + +/* + * This handler is for the remote version of pmGetChildren. + */ +int +DoPMNSChild(ClientInfo *cp, __pmPDU *pb) +{ + int sts = 0; + int numnames = 0; + char *name = NULL; + char **offspring = NULL; + int *statuslist = NULL; + int subtype; + char *namelist[1]; + pmID idlist[1]; + + if ((sts = __pmDecodeChildReq(pb, &name, &subtype)) < 0) + goto done; + + namelist[0] = name; + sts = pmLookupName(1, namelist, idlist); + if (sts == 1 && pmid_domain(idlist[0]) == DYNAMIC_PMID && pmid_item(idlist[0]) == 0) { + int domain = pmid_cluster(idlist[0]); + AgentInfo *ap = NULL; + if ((ap = FindDomainAgent(domain)) == NULL) { + sts = PM_ERR_NOAGENT; + goto done; + } + if (!ap->status.connected) { + sts = PM_ERR_NOAGENT; + goto done; + } + if (ap->ipcType == AGENT_DSO) { + if (ap->ipc.dso.dispatch.comm.pmda_interface >= PMDA_INTERFACE_5) + ap->ipc.dso.dispatch.version.four.ext->e_context = cp - client; + if (ap->ipc.dso.dispatch.comm.pmda_interface >= PMDA_INTERFACE_4) { + sts = ap->ipc.dso.dispatch.version.four.children(name, 0, &offspring, &statuslist, + ap->ipc.dso.dispatch.version.four.ext); + if (sts < 0) + goto done; + if (subtype == 0) { + if (statuslist) free(statuslist); + statuslist = NULL; + } + } + else { + /* Not PMDA_INTERFACE_4 or later */ + sts = PM_ERR_NAME; + goto done; + } + } + else { + /* daemon PMDA ... ship request on */ + int fdfail = -1; + if (ap->status.notReady) + sts = PM_ERR_AGAIN; + else { + pmcd_trace(TR_XMIT_PDU, ap->inFd, PDU_PMNS_CHILD, 1); + sts = __pmSendChildReq(ap->inFd, cp - client, name, subtype); + if (sts >= 0) { + int pinpdu; + pinpdu = sts = __pmGetPDU(ap->outFd, ANY_SIZE, _pmcd_timeout, &pb); + if (sts > 0) + pmcd_trace(TR_RECV_PDU, ap->outFd, sts, (int)((__psint_t)pb & 0xffffffff)); + if (sts == PDU_PMNS_NAMES) { + sts = __pmDecodeNameList(pb, &numnames, + &offspring, &statuslist); + if (sts >= 0) { + sts = numnames; + if (subtype == 0) { + free(statuslist); + statuslist = NULL; + } + } + } + else if (sts == PDU_ERROR) { + __pmDecodeError(pb, &sts); + pmcd_trace(TR_RECV_ERR, ap->outFd, PDU_PMNS_NAMES, sts); + } + else { + pmcd_trace(TR_WRONG_PDU, ap->outFd, PDU_PMNS_NAMES, sts); + sts = PM_ERR_IPC; /* Wrong PDU type */ + fdfail = ap->outFd; + } + if (pinpdu > 0) + __pmUnpinPDUBuf(pb); + } + else { + /* __pmSendChildReq failed */ + sts = __pmMapErrno(sts); + fdfail = ap->inFd; + } + } + if (ap != NULL && ap->ipcType != AGENT_DSO && + (sts == PM_ERR_IPC || sts == PM_ERR_TIMEOUT || sts == -EPIPE) && + fdfail != -1) + CleanupAgent(ap, AT_COMM, fdfail); + } + } + else { + if (subtype == 0) { + if ((sts = pmGetChildren(name, &offspring)) < 0) + goto done; + } + else { + if ((sts = pmGetChildrenStatus(name, &offspring, &statuslist)) < 0) + goto done; + } + } + + numnames = sts; + pmcd_trace(TR_XMIT_PDU, cp->fd, PDU_PMNS_NAMES, numnames); + if ((sts = __pmSendNameList(cp->fd, FROM_ANON, numnames, offspring, statuslist)) < 0) { + pmcd_trace(TR_XMIT_ERR, cp->fd, PDU_PMNS_NAMES, sts); + CleanupClient(cp, sts); + } + +done: + if (name) free(name); + if (offspring) free(offspring); + if (statuslist) free(statuslist); + return sts; +} + +/*************************************************************************/ + +static char **travNL; /* list of names for traversal */ +static char *travNL_ptr; /* pointer into travNL */ +static int travNL_num; /* number of names in list */ +static int travNL_strlen; /* number of bytes of names */ +static int travNL_i; /* array index */ + +static void +AddLengths(const char *name) +{ + travNL_strlen += strlen(name) + 1; + travNL_num++; +} + +static void +BuildNameList(const char *name) +{ + travNL[travNL_i++] = travNL_ptr; + strcpy(travNL_ptr, name); + travNL_ptr += strlen(name) + 1; +} + +/* + * handle dynamic PMNS entries in remote version of pmTraversePMNS. + * + * num_names and names[] is the result of pmTraversePMNS for the + * loaded PMNS ... need to preserve the semantics of this in the + * end result, so names[] and all of the name[i] strings are in a + * single malloc block + */ +static void +traverse_dynamic(ClientInfo *cp, char *start, int *num_names, char ***names) +{ + int sts; + int i; + char **offspring; + int *statuslist; + char *namelist[1]; + pmID idlist[1]; + int fake = 0; + + /* + * if we get any errors in the setup (unexpected), simply skip + * that name[i] entry and move on ... any client using the associated + * name[i] will get an error later, e.g. when trying to fetch the + * pmDesc + * + * process in reverse order so stitching does not disturb the ones + * we've not processed yet + */ + if (*num_names == 0) { + /* + * special case, where starting point is _below_ the dynamic + * node in the PMNS known to pmcd (or name is simply invalid) ... + * fake a single name in the list so far ... names[] does not hold + * the string value as well, but this is OK because names[0] will + * be rebuilt * replacing "name" (or cleaned up at the end) ... + * note travNL_strlen initialization so resize below is correct + */ + fake = 1; + *names = (char **)malloc(sizeof((*names)[0])); + if (*names == NULL) + return; + (*names)[0] = start; + *num_names = 1; + travNL_strlen = strlen(start) + 1; + } + for (i = *num_names-1; i >= 0; i--) { + offspring = NULL; + namelist[0] = (*names)[i]; + sts = pmLookupName(1, namelist, idlist); + if (sts < 1) + continue; + if (pmid_domain(idlist[0]) == DYNAMIC_PMID && pmid_item(idlist[0]) == 0) { + int domain = pmid_cluster(idlist[0]); + AgentInfo *ap; + if ((ap = FindDomainAgent(domain)) == NULL) + continue; + if (!ap->status.connected) + continue; + if (ap->ipcType == AGENT_DSO) { + if (ap->ipc.dso.dispatch.comm.pmda_interface >= PMDA_INTERFACE_5) + ap->ipc.dso.dispatch.version.four.ext->e_context = cp - client; + if (ap->ipc.dso.dispatch.comm.pmda_interface >= PMDA_INTERFACE_4) { + sts = ap->ipc.dso.dispatch.version.four.children(namelist[0], 1, &offspring, &statuslist, + ap->ipc.dso.dispatch.version.four.ext); +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_PMNS) { + fprintf(stderr, "traverse_dynamic: DSO PMDA: expand dynamic PMNS entry %s (%s) -> ", namelist[0], pmIDStr(idlist[0])); + if (sts < 0) + fprintf(stderr, "%s\n", pmErrStr(sts)); + else { + int j; + fprintf(stderr, "%d names\n", sts); + for (j = 0; j < sts; j++) { + fprintf(stderr, " %s\n", offspring[j]); + } + } + } +#endif + if (sts < 0) + continue; + if (statuslist) free(statuslist); + } + else { + /* Not PMDA_INTERFACE_4 or later */ + continue; + } + } + else { + /* daemon PMDA ... ship request on */ + int fdfail = -1; + if (ap->status.notReady) + continue; + pmcd_trace(TR_XMIT_PDU, ap->inFd, PDU_PMNS_TRAVERSE, 1); + sts = __pmSendTraversePMNSReq(ap->inFd, cp - client, namelist[0]); + if (sts >= 0) { + int numnames; + __pmPDU *pb; + int pinpdu; + pinpdu = sts = __pmGetPDU(ap->outFd, ANY_SIZE, _pmcd_timeout, &pb); + if (sts > 0) + pmcd_trace(TR_RECV_PDU, ap->outFd, sts, (int)((__psint_t)pb & 0xffffffff)); + if (sts == PDU_PMNS_NAMES) { + sts = __pmDecodeNameList(pb, &numnames, + &offspring, &statuslist); +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_PMNS) { + fprintf(stderr, "traverse_dynamic: daemon PMDA: expand dynamic PMNS entry %s (%s) -> ", namelist[0], pmIDStr(idlist[0])); + if (sts < 0) + fprintf(stderr, "%s\n", pmErrStr(sts)); + else { + int j; + fprintf(stderr, "%d names\n", sts); + for (j = 0; j < sts; j++) { + fprintf(stderr, " %s\n", offspring[j]); + } + } + } +#endif + if (statuslist) { + free(statuslist); + statuslist = NULL; + } + if (sts >= 0) { + sts = numnames; + } + } + else if (sts == PDU_ERROR) { + __pmDecodeError(pb, &sts); + pmcd_trace(TR_RECV_ERR, ap->outFd, PDU_PMNS_NAMES, sts); + } + else { + pmcd_trace(TR_WRONG_PDU, ap->outFd, PDU_PMNS_IDS, sts); + sts = PM_ERR_IPC; /* Wrong PDU type */ + fdfail = ap->outFd; + } + if (pinpdu > 0) + __pmUnpinPDUBuf(pb); + } + else { + /* __pmSendChildReq failed */ + sts = __pmMapErrno(sts); + fdfail = ap->inFd; + } + if (ap != NULL && ap->ipcType != AGENT_DSO && + (sts == PM_ERR_IPC || sts == PM_ERR_TIMEOUT || sts == -EPIPE) && + fdfail != -1) + CleanupAgent(ap, AT_COMM, fdfail); + } + } + /* Stitching ... remove names[i] and add sts names from offspring[] */ + if (offspring) { + int j; + int k; /* index for copying to new[] */ + int ii; /* index for copying from names[] */ + char **new; + char *p; /* string copy dest ptr */ + int new_len; + + fake = 0; /* don't need to undo faking */ + new_len = travNL_strlen - strlen(namelist[0]) - 1; + for (j = 0; j < sts; j++) + new_len += strlen(offspring[j]) + 1; + new = (char **)malloc(new_len + (*num_names - 1 + sts)*sizeof(new[0])); + if (new == NULL) { + /* tough luck! */ + free(offspring); + continue; + } + *num_names = *num_names - 1 + sts; + p = (char *)&new[*num_names]; + ii = 0; + for (k = 0; k < *num_names; k++) { + if (k < i || k >= i+sts) { + /* copy across old name */ + if (k == i+sts) + ii++; /* skip name than new ones replaced */ + strcpy(p, (*names)[ii]); + ii++; + } + else { + /* stitch in new name */ + strcpy(p, offspring[k-i]); + } + new[k] = p; + p += strlen(p) + 1; + } + + free(offspring); + free(*names); + *names = new; + travNL_strlen = new_len; + } + } + + if (fake == 1) { + /* + * need to undo initial faking as this name is simply not valid! + */ + *num_names = 0; + free(*names); + *names = NULL; + travNL_strlen = 0; + } + +} + +/* + * This handler is for the remote version of pmTraversePMNS. + * + * Notes: + * We are building up a name-list and giving it to + * __pmSendNameList. + * This is a bit inefficient but convenient. + * It would really be better to build up a PDU buffer + * directly and not do the extra copying ! + */ +int +DoPMNSTraverse(ClientInfo *cp, __pmPDU *pb) +{ + int sts = 0; + char *name = NULL; + int travNL_need = 0; + + travNL = NULL; + + if ((sts = __pmDecodeTraversePMNSReq(pb, &name)) < 0) + goto done; + + travNL_strlen = 0; + travNL_num = 0; + if ((sts = pmTraversePMNS(name, AddLengths)) < 0) + goto check; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_PMNS) { + fprintf(stderr, "DoPMNSTraverse: %d names below %s after pmTraversePMNS\n", travNL_num, name); + } +#endif + + /* for each ptr, string bytes, and string terminators */ + travNL_need = travNL_num * (int)sizeof(char*) + travNL_strlen; + + if ((travNL = (char**)malloc(travNL_need)) == NULL) { + sts = -oserror(); + goto done; + } + + travNL_i = 0; + travNL_ptr = (char*)&travNL[travNL_num]; + sts = pmTraversePMNS(name, BuildNameList); + +check: + /* + * sts here is last result of calling pmTraversePMNS() ... may need + * this later + * for dynamic PMNS entries, travNL_num will be 0 (PM_ERR_PMID from + * pmTraversePMNS()). + */ + traverse_dynamic(cp, name, &travNL_num, &travNL); + if (travNL_num < 1) + goto done; + + pmcd_trace(TR_XMIT_PDU, cp->fd, PDU_PMNS_NAMES, travNL_num); + if ((sts = __pmSendNameList(cp->fd, FROM_ANON, travNL_num, travNL, NULL)) < 0) { + pmcd_trace(TR_XMIT_ERR, cp->fd, PDU_PMNS_NAMES, sts); + CleanupClient(cp, sts); + goto done; + } + +done: + if (name) free(name); + if (travNL) free(travNL); + return sts; +} + +/*************************************************************************/ + +int +DoCreds(ClientInfo *cp, __pmPDU *pb) +{ + int i, sts, flags = 0, version = 0, sender = 0, credcount = 0; + __pmCred *credlist = NULL; + __pmVersionCred *vcp; + + if ((sts = __pmDecodeCreds(pb, &sender, &credcount, &credlist)) < 0) + return sts; + pmcd_trace(TR_RECV_PDU, cp->fd, PDU_CREDS, credcount); + + for (i = 0; i < credcount; i++) { + switch(credlist[i].c_type) { + case CVERSION: + vcp = (__pmVersionCred *)&credlist[i]; + flags = vcp->c_flags; + version = vcp->c_version; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) + fprintf(stderr, "pmcd: version cred (%u) flags=%x\n", vcp->c_version, vcp->c_flags); +#endif + break; + + default: +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) + fprintf(stderr, "pmcd: Error: bogus cred type %d\n", credlist[i].c_type); +#endif + sts = PM_ERR_IPC; + break; + } + } + if (credlist != NULL) + free(credlist); + + if (sts >= 0 && version) + sts = __pmSetVersionIPC(cp->fd, version); + if (sts >= 0 && flags) { + /* + * new client has arrived; may want encryption, authentication, etc + * complete the handshake (depends on features requested), continue + * on to check access is allowed for the authenticated persona, and + * finally notify any interested PMDAs + */ + if ((sts = __pmSecureServerHandshake(cp->fd, flags, &cp->attrs)) < 0) + return sts; + } + if ((sts = CheckAccountAccess(cp)) < 0) /* host access done already */ + return sts; + else if (sts > 0) /* account authentication successful - inform PMDAs */ + sts = AgentsAuthentication(cp - client); + /* else: no account-based authentication needed, so finish successfully */ + + return sts; +} diff --git a/src/pmcd/src/dostore.c b/src/pmcd/src/dostore.c new file mode 100644 index 0000000..8e6071a --- /dev/null +++ b/src/pmcd/src/dostore.c @@ -0,0 +1,312 @@ +/* + * Copyright (c) 2012-2013 Red Hat. + * Copyright (c) 1995-2002 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. + */ + +#include "pmapi.h" +#include "impl.h" +#include "pmcd.h" +#include <assert.h> + +/* Routine to split a result into a list of results, each containing metrics + * from a single domain. The end of the list is marked by a pmResult with a + * numpmid of zero. Any pmids for which there is no agent will be in the + * second to last pmResult which will have a negated numpmid value. + */ + +pmResult ** +SplitResult(pmResult *res) +{ + int i, j; + static int *aFreq = NULL; /* Freq. histogram: pmids for each agent */ + static int *resIndex = NULL; /* resIndex[k] = index of agent[k]'s list in result */ + static int nDoms = 0; /* No. of entries in two tables above */ + int nGood; + int need; + pmResult **results; + + /* Allocate the frequency histogram and array for mapping from agent to + * result list index. Because a SIGHUP reconfiguration may have caused a + * change in the number of agents, reallocation using a new size may be + * necessary. + * There are nAgents + 1 entries in the aFreq and resIndex arrays. The + * last entry in each is used for the pmIDs for which no agent could be + * found. + */ + if (nAgents > nDoms) { + nDoms = nAgents; + if (aFreq != NULL) + free(aFreq); + if (resIndex != NULL) + free(resIndex); + aFreq = (int *)malloc((nAgents + 1) * sizeof(int)); + resIndex = (int *)malloc((nAgents + 1) * sizeof(int)); + if (aFreq == NULL || resIndex == NULL) { + __pmNoMem("SplitResult.freq", 2 * (nAgents + 1) * sizeof(int), PM_FATAL_ERR); + } + } + + /* Build a frequency histogram of metric domains (use aFreq[nAgents] for + * pmids for which there is no agent). + */ + for (i = 0; i <= nAgents; i++) + aFreq[i] = 0; + for (i = 0; i < res->numpmid; i++) { + int dom = ((__pmID_int *)&res->vset[i]->pmid)->domain; + for (j = 0; j < nAgents; j++) + if (agent[j].pmDomainId == dom && agent[j].status.connected) + break; + aFreq[j]++; + } + + /* Initialise resIndex and allocate the results structures */ + nGood = 0; + for (i = 0; i < nAgents; i++) + if (aFreq[i]) { + resIndex[i] = nGood; + nGood++; + } + resIndex[nAgents] = nGood; + + need = nGood + 1 + ((aFreq[nAgents]) ? 1 : 0); + need *= sizeof(pmResult *); + if ((results = (pmResult **) malloc(need)) == NULL) { + __pmNoMem("SplitResult.results", need, PM_FATAL_ERR); + } + j = 0; + for (i = 0; i <= nAgents; i++) + if (aFreq[i]) { + need = (int)sizeof(pmResult) + (aFreq[i] - 1) * (int)sizeof(pmValueSet *); + results[j] = (pmResult *) malloc(need); + if (results[j] == NULL) { + __pmNoMem("SplitResult.domain", need, PM_FATAL_ERR); + } + results[j]->numpmid = aFreq[i]; + j++; + } + + /* Make the "end of list" pmResult */ + if ((results[j] = (pmResult *) malloc(sizeof(pmResult))) == NULL) { + __pmNoMem("SplitResult.domain", sizeof(pmResult), PM_FATAL_ERR); + } + results[j]->numpmid = 0; + + /* Foreach vset in res, find it's pmResult in the per domain results array + * and copy a pointer to the vset to the next available position in the per + * domain result. + */ + for (i = 0; i <= nAgents; i++) + aFreq[i] = 0; + for (i = 0; i < res->numpmid; i++) { + int dom = ((__pmID_int *)&res->vset[i]->pmid)->domain; + for (j = 0; j < nAgents; j++) + if (dom == agent[j].pmDomainId && agent[j].status.connected) + break; + results[resIndex[j]]->vset[aFreq[j]] = res->vset[i]; + aFreq[j]++; + } + + /* Flip the sign of numpmids in the "bad list" */ + if (aFreq[nAgents]) { + int bad = resIndex[nAgents]; + results[bad]->numpmid = -results[bad]->numpmid; + } + + return results; +} + +int +DoStore(ClientInfo *cp, __pmPDU* pb) +{ + int sts; + int s = 0; + AgentInfo *ap; + pmResult *result; + pmResult **dResult; + int i; + __pmFdSet readyFds; + __pmFdSet waitFds; + int nWait = 0; + int maxFd = -1; + int badStore; /* != 0 => store to nonexistent agent */ + int notReady = 0; /* != 0 => store to agent that's not ready */ + struct timeval timeout; + + + if ((sts = __pmDecodeResult(pb, &result)) < 0) + return sts; + + dResult = SplitResult(result); + + /* Send the per-domain results to their respective agents */ + + __pmFD_ZERO(&waitFds); + for (i = 0; dResult[i]->numpmid > 0; i++) { + int fd; + ap = FindDomainAgent(((__pmID_int *)&dResult[i]->vset[0]->pmid)->domain); + /* If it's in a "good" list, pmID has agent that is connected */ + assert(ap != NULL); + + if (ap->ipcType == AGENT_DSO) { + if (ap->ipc.dso.dispatch.comm.pmda_interface >= PMDA_INTERFACE_5) + ap->ipc.dso.dispatch.version.four.ext->e_context = cp - client; + s = ap->ipc.dso.dispatch.version.any.store(dResult[i], + ap->ipc.dso.dispatch.version.any.ext); + } + else { + if (ap->status.notReady == 0) { + /* agent is ready for PDUs */ + pmcd_trace(TR_XMIT_PDU, ap->inFd, PDU_RESULT, dResult[i]->numpmid); + s = __pmSendResult(ap->inFd, cp - client, dResult[i]); + if (s >= 0) { + ap->status.busy = 1; + fd = ap->outFd; + __pmFD_SET(fd, &waitFds); + if (fd > maxFd) + maxFd = fd; + nWait++; + } + else if (s == PM_ERR_IPC || sts == PM_ERR_TIMEOUT || s == -EPIPE) { + pmcd_trace(TR_XMIT_ERR, ap->inFd, PDU_RESULT, sts); + CleanupAgent(ap, AT_COMM, ap->inFd); + } + } + else + /* agent is not ready for PDUs */ + notReady = 1; + } + if (s < 0) { + sts = s; + continue; + } + } + + /* If there was no agent for one or more pmIDs, there will be a "bad list" + * with a negated numpmid value. Store as many "good" pmIDs as possible + * but remember that there were homeless ones. + */ + badStore = dResult[i]->numpmid < 0; + + /* Collect error PDUs containing store status from each active agent */ + + while (nWait > 0) { + __pmFD_COPY(&readyFds, &waitFds); + if (nWait > 1) { + timeout.tv_sec = _pmcd_timeout; + timeout.tv_usec = 0; + + s = __pmSelectRead(maxFd+1, &readyFds, &timeout); + + if (s == 0) { + __pmNotifyErr(LOG_INFO, "DoStore: select timeout"); + + /* Timeout, terminate agents that haven't responded */ + for (i = 0; i < nAgents; i++) { + if (agent[i].status.busy) { + pmcd_trace(TR_RECV_TIMEOUT, agent[i].outFd, PDU_ERROR, 0); + CleanupAgent(&agent[i], AT_COMM, agent[i].inFd); + } + } + sts = PM_ERR_IPC; + break; + } + else if (sts < 0) { + /* this is not expected to happen! */ + __pmNotifyErr(LOG_ERR, "DoStore: fatal select failure: %s\n", + netstrerror()); + Shutdown(); + exit(1); + } + } + + for (i = 0; i < nAgents; i++) { + int pinpdu; + ap = &agent[i]; + if (!ap->status.busy || !__pmFD_ISSET(ap->outFd, &readyFds)) + continue; + ap->status.busy = 0; + __pmFD_CLR(ap->outFd, &waitFds); + nWait--; + pinpdu = s = __pmGetPDU(ap->outFd, ANY_SIZE, _pmcd_timeout, &pb); + if (s > 0) + pmcd_trace(TR_RECV_PDU, ap->outFd, s, (int)((__psint_t)pb & 0xffffffff)); + if (s == PDU_ERROR) { + int ss; + if ((ss = __pmDecodeError(pb, &s)) < 0) + sts = ss; + else { + if (s < 0) { + extern int CheckError(AgentInfo *, int); + + sts = CheckError(ap, s); + pmcd_trace(TR_RECV_ERR, ap->outFd, PDU_RESULT, sts); + } + } + } + else { + /* Agent protocol error */ + if (s < 0) + pmcd_trace(TR_RECV_ERR, ap->outFd, PDU_RESULT, s); + else + pmcd_trace(TR_WRONG_PDU, ap->outFd, PDU_ERROR, s); + sts = PM_ERR_IPC; + } + + if (pinpdu > 0) + __pmUnpinPDUBuf(pb); + + if (ap->ipcType != AGENT_DSO && + (sts == PM_ERR_IPC || sts == PM_ERR_TIMEOUT)) + CleanupAgent(ap, AT_COMM, ap->outFd); + } + } + + /* Only one error code can be returned, so "no agent" or "not + * ready" errors have precedence over all except IPC and TIMEOUT + * protocol failures. + * Note that we make only a weak effort to return the most + * appropriate error status because clients interested in the + * outcome should be using pmStore on individual metric/instances + * if the outcome is important. In particular, in multi-agent + * stores, an earlier PM_ERR_IPC error can be "overwritten" by a + * subsequent less serious error. + */ + if (sts != PM_ERR_IPC && sts != PM_ERR_TIMEOUT) { + if (badStore) { + sts = PM_ERR_NOAGENT; + } + else if (notReady) { + sts = PM_ERR_AGAIN; + } + } + + if (sts >= 0) { + /* send PDU_ERROR, even if result was 0 */ + int s; + pmcd_trace(TR_XMIT_PDU, cp->fd, PDU_ERROR, 0); + s = __pmSendError(cp->fd, FROM_ANON, 0); + if (s < 0) + CleanupClient(cp, s); + } + + pmFreeResult(result); + i = 0; + do { + s = dResult[i]->numpmid; + free(dResult[i]); + i++; + } while (s); /* numpmid == 0 terminates list */ + free(dResult); + + return sts; +} diff --git a/src/pmcd/src/pmcd.c b/src/pmcd/src/pmcd.c new file mode 100644 index 0000000..1bafee5 --- /dev/null +++ b/src/pmcd/src/pmcd.c @@ -0,0 +1,1024 @@ +/* + * Copyright (c) 2012-2014 Red Hat. + * Copyright (c) 1995-2001,2004 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. + */ + +#include "pmcd.h" +#include "impl.h" +#include <sys/stat.h> +#include <assert.h> + +#define SHUTDOWNWAIT 12 /* < PMDAs wait previously used in rc_pcp */ +#define MAXPENDING 5 /* maximum number of pending connections */ +#define FDNAMELEN 40 /* maximum length of a fd description */ +#define STRINGIFY(s) #s +#define TO_STRING(s) STRINGIFY(s) + +static char *FdToString(int); +static void ResetBadHosts(void); + +int AgentDied; /* for updating mapdom[] */ +static int timeToDie; /* For SIGINT handling */ +static int restart; /* For SIGHUP restart */ +static int maxReqPortFd; /* Largest request port fd */ +static char configFileName[MAXPATHLEN]; /* path to pmcd.conf */ +static char *logfile = "pmcd.log"; /* log file name */ +static int run_daemon = 1; /* run as a daemon, see -f */ +int _creds_timeout = 3; /* Timeout for agents credential PDU */ +static char *fatalfile = "/dev/tty";/* fatal messages at startup go here */ +static char *pmnsfile = PM_NS_DEFAULT; +static char *username; +static char *certdb; /* certificate database path (NSS) */ +static char *dbpassfile; /* certificate database password file */ +static int dupok; /* set to 1 for -N pmnsfile */ +static char sockpath[MAXPATHLEN]; /* local unix domain socket path */ + +#ifdef HAVE_SA_SIGINFO +static pid_t killer_pid; +static uid_t killer_uid; +#endif +static int killer_sig; + +static void +DontStart(void) +{ + FILE *tty; + FILE *log; + + __pmNotifyErr(LOG_ERR, "pmcd not started due to errors!\n"); + + if ((tty = fopen(fatalfile, "w")) != NULL) { + fflush(stderr); + fprintf(tty, "NOTE: pmcd not started due to errors! "); + if ((log = fopen(logfile, "r")) != NULL) { + int c; + fprintf(tty, "Log file \"%s\" contains ...\n", logfile); + while ((c = fgetc(log)) != EOF) + fputc(c, tty); + fclose(log); + } + else + fprintf(tty, "Log file \"%s\" has vanished!\n", logfile); + fclose(tty); + } + /* + * We are often called after the request ports have been opened. If we don't + * explicitely close them, then the unix domain socket file (if any) will be + * left in the file system, causing "address already in use" the next time + * pmcd starts. + */ + __pmServerCloseRequestPorts(); + + exit(1); +} + +static pmLongOptions longopts[] = { + PMAPI_OPTIONS_HEADER("General options"), + PMOPT_DEBUG, + PMOPT_NAMESPACE, + PMOPT_DUPNAMES, + PMOPT_HELP, + PMAPI_OPTIONS_HEADER("Service options"), + { "", 0, 'A', 0, "disable service advertisement" }, + { "foreground", 0, 'f', 0, "run in the foreground" }, + { "hostname", 1, 'H', "HOST", "set the hostname to be used for pmcd.hostname metric" }, + { "username", 1, 'U', "USER", "in daemon mode, run as named user [default pcp]" }, + PMAPI_OPTIONS_HEADER("Configuration options"), + { "config", 1, 'c', "PATH", "path to configuration file" }, + { "certdb", 1, 'C', "PATH", "path to NSS certificate database" }, + { "passfile", 1, 'P', "PATH", "password file for certificate database access" }, + { "", 1, 'L', "BYTES", "maximum size for PDUs from clients [default 65536]" }, + { "", 1, 'q', "TIME", "PMDA initial negotiation timeout (seconds) [default 3]" }, + { "", 1, 't', "TIME", "PMDA response timeout (seconds) [default 5]" }, + PMAPI_OPTIONS_HEADER("Connection options"), + { "interface", 1, 'i', "ADDR", "accept connections on this IP address" }, + { "port", 1, 'p', "N", "accept connections on this port" }, + { "socket", 1, 's', "PATH", "Unix domain socket file [default $PCP_RUN_DIR/pmcd.socket]" }, + PMAPI_OPTIONS_HEADER("Diagnostic options"), + { "trace", 1, 'T', "FLAG", "Event trace control" }, + { "log", 1, 'l', "PATH", "redirect diagnostics and trace output" }, + { "", 1, 'x', "PATH", "fatal messages at startup sent to file [default /dev/tty]" }, + PMAPI_OPTIONS_END +}; + +static pmOptions opts = { + .flags = PM_OPTFLAG_POSIX, + .short_options = "Ac:C:D:fH:i:l:L:N:n:p:P:q:s:St:T:U:x:?", + .long_options = longopts, +}; + +static void +ParseOptions(int argc, char *argv[], int *nports) +{ + int c; + int sts; + char *endptr; + int usage = 0; + int val; + + endptr = pmGetConfig("PCP_PMCDCONF_PATH"); + strncpy(configFileName, endptr, sizeof(configFileName)-1); + + while ((c = pmgetopt_r(argc, argv, &opts)) != EOF) { + switch (c) { + + case 'A': /* disable pmcd service advertising */ + __pmServerClearFeature(PM_SERVER_FEATURE_DISCOVERY); + break; + + case 'c': /* configuration file */ + strncpy(configFileName, opts.optarg, sizeof(configFileName)-1); + break; + + case 'C': /* path to NSS certificate database */ + certdb = opts.optarg; + break; + + case 'D': /* debug flag */ + sts = __pmParseDebug(opts.optarg); + if (sts < 0) { + pmprintf("%s: unrecognized debug flag specification (%s)\n", + pmProgname, opts.optarg); + opts.errors++; + } + pmDebug |= sts; + break; + + case 'f': + /* foreground, i.e. do _not_ run as a daemon */ + run_daemon = 0; + break; + + case 'i': + /* one (of possibly several) interfaces for client requests */ + __pmServerAddInterface(opts.optarg); + break; + + case 'H': + /* use the given name as the pmcd.hostname for this host */ + _pmcd_hostname = opts.optarg; + break; + + case 'l': + /* log file name */ + logfile = opts.optarg; + break; + + case 'L': /* Maximum size for PDUs from clients */ + val = (int)strtol(opts.optarg, NULL, 0); + if (val <= 0) { + pmprintf("%s: -L requires a positive value\n", pmProgname); + opts.errors++; + } else { + __pmSetPDUCeiling(val); + } + break; + + case 'N': + dupok = 1; + /*FALLTHROUGH*/ + case 'n': + /* name space file name */ + pmnsfile = opts.optarg; + break; + + case 'p': + if (__pmServerAddPorts(opts.optarg) < 0) { + pmprintf("%s: -p requires a positive numeric argument (%s)\n", + pmProgname, opts.optarg); + opts.errors++; + } else { + *nports += 1; + } + break; + + case 'P': /* password file for certificate database access */ + dbpassfile = opts.optarg; + break; + + case 'q': + val = (int)strtol(opts.optarg, &endptr, 10); + if (*endptr != '\0' || val <= 0.0) { + pmprintf("%s: -q requires a positive numeric argument\n", + pmProgname); + opts.errors++; + } else { + _creds_timeout = val; + } + break; + + case 's': /* path to local unix domain socket */ + snprintf(sockpath, sizeof(sockpath), "%s", opts.optarg); + break; + + case 'S': /* only allow authenticated clients */ + __pmServerSetFeature(PM_SERVER_FEATURE_CREDS_REQD); + break; + + case 't': + val = (int)strtol(opts.optarg, &endptr, 10); + if (*endptr != '\0' || val < 0.0) { + pmprintf("%s: -t requires a positive numeric argument\n", + pmProgname); + opts.errors++; + } else { + _pmcd_timeout = val; + } + break; + + case 'T': + val = (int)strtol(opts.optarg, &endptr, 10); + if (*endptr != '\0' || val < 0) { + pmprintf("%s: -T requires a positive numeric argument\n", + pmProgname); + opts.errors++; + } else { + _pmcd_trace_mask = val; + } + break; + + case 'U': + username = opts.optarg; + break; + + case 'x': + fatalfile = opts.optarg; + break; + + case '?': + usage = 1; + break; + + default: + opts.errors++; + break; + } + } + + if (usage || opts.errors || opts.optind < argc) { + pmUsageMessage(&opts); + if (usage) + exit(0); + DontStart(); + } +} + +/* + * Determine which clients (if any) have sent data to the server and handle it + * as required. + */ +void +HandleClientInput(__pmFdSet *fdsPtr) +{ + int sts; + int i; + __pmPDU *pb; + __pmPDUHdr *php; + ClientInfo *cp; + + for (i = 0; i < nClients; i++) { + int pinpdu; + if (!client[i].status.connected || !__pmFD_ISSET(client[i].fd, fdsPtr)) + continue; + + cp = &client[i]; + this_client_id = i; + + pinpdu = sts = __pmGetPDU(cp->fd, LIMIT_SIZE, _pmcd_timeout, &pb); + if (sts > 0) { + pmcd_trace(TR_RECV_PDU, cp->fd, sts, (int)((__psint_t)pb & 0xffffffff)); + } else { + CleanupClient(cp, sts); + continue; + } + + php = (__pmPDUHdr *)pb; + if (__pmVersionIPC(cp->fd) == UNKNOWN_VERSION && php->type != PDU_CREDS) { + /* old V1 client protocol, no longer supported */ + sts = PM_ERR_IPC; + CleanupClient(cp, sts); + __pmUnpinPDUBuf(pb); + continue; + } + + if (pmDebug & DBG_TRACE_APPL0) + ShowClients(stderr); + + switch (php->type) { + case PDU_PROFILE: + sts = (cp->denyOps & PMCD_OP_FETCH) ? + PM_ERR_PERMISSION : DoProfile(cp, pb); + break; + + case PDU_FETCH: + sts = (cp->denyOps & PMCD_OP_FETCH) ? + PM_ERR_PERMISSION : DoFetch(cp, pb); + break; + + case PDU_INSTANCE_REQ: + sts = (cp->denyOps & PMCD_OP_FETCH) ? + PM_ERR_PERMISSION : DoInstance(cp, pb); + break; + + case PDU_DESC_REQ: + sts = (cp->denyOps & PMCD_OP_FETCH) ? + PM_ERR_PERMISSION : DoDesc(cp, pb); + break; + + case PDU_TEXT_REQ: + sts = (cp->denyOps & PMCD_OP_FETCH) ? + PM_ERR_PERMISSION : DoText(cp, pb); + break; + + case PDU_RESULT: + sts = (cp->denyOps & PMCD_OP_STORE) ? + PM_ERR_PERMISSION : DoStore(cp, pb); + break; + + case PDU_PMNS_IDS: + sts = (cp->denyOps & PMCD_OP_FETCH) ? + PM_ERR_PERMISSION : DoPMNSIDs(cp, pb); + break; + + case PDU_PMNS_NAMES: + sts = (cp->denyOps & PMCD_OP_FETCH) ? + PM_ERR_PERMISSION : DoPMNSNames(cp, pb); + break; + + case PDU_PMNS_CHILD: + sts = (cp->denyOps & PMCD_OP_FETCH) ? + PM_ERR_PERMISSION : DoPMNSChild(cp, pb); + break; + + case PDU_PMNS_TRAVERSE: + sts = (cp->denyOps & PMCD_OP_FETCH) ? + PM_ERR_PERMISSION : DoPMNSTraverse(cp, pb); + break; + + case PDU_CREDS: + sts = DoCreds(cp, pb); + break; + + default: + sts = PM_ERR_IPC; + } + if (sts < 0) { + if (pmDebug & DBG_TRACE_APPL0) + fprintf(stderr, "PDU: %s client[%d]: %s\n", + __pmPDUTypeStr(php->type), i, pmErrStr(sts)); + /* Make sure client still alive before sending. */ + if (cp->status.connected) { + pmcd_trace(TR_XMIT_PDU, cp->fd, PDU_ERROR, sts); + sts = __pmSendError(cp->fd, FROM_ANON, sts); + if (sts < 0) + __pmNotifyErr(LOG_ERR, "HandleClientInput: " + "error sending Error PDU to client[%d] %s\n", i, pmErrStr(sts)); + } + } + if (pinpdu > 0) + __pmUnpinPDUBuf(pb); + } +} + +/* Called to shutdown pmcd in an orderly manner */ + +void +Shutdown(void) +{ + int i; + + for (i = 0; i < nAgents; i++) { + AgentInfo *ap = &agent[i]; + if (!ap->status.connected) + continue; + if (ap->inFd != -1) { + if (__pmSocketIPC(ap->inFd)) + __pmCloseSocket(ap->inFd); + else + close(ap->inFd); + } + if (ap->outFd != -1) { + if (__pmSocketIPC(ap->outFd)) + __pmCloseSocket(ap->outFd); + else + close(ap->outFd); + } + if (ap->ipcType == AGENT_SOCKET && + ap->ipc.socket.addrDomain == AF_UNIX) { + /* remove the Unix domain socket */ + unlink(ap->ipc.socket.name); + } + } + if (HarvestAgents(SHUTDOWNWAIT) < 0) { + /* terminate with prejudice any still remaining */ + for (i = 0; i < nAgents; i++) { + AgentInfo *ap = &agent[i]; + if (ap->status.connected) { + pid_t pid = ap->ipcType == AGENT_SOCKET ? + ap->ipc.socket.agentPid : ap->ipc.pipe.agentPid; + __pmProcessTerminate(pid, 1); + } + } + } + for (i = 0; i < nClients; i++) + if (client[i].status.connected) + __pmCloseSocket(client[i].fd); + __pmServerCloseRequestPorts(); + __pmSecureServerShutdown(); + __pmNotifyErr(LOG_INFO, "pmcd Shutdown\n"); + fflush(stderr); +} + +static void +SignalShutdown(void) +{ +#ifdef HAVE_SA_SIGINFO +#if DESPERATE + char buf[256]; +#endif + if (killer_pid != 0) { + __pmNotifyErr(LOG_INFO, "pmcd caught %s from pid=%" FMT_PID " uid=%d\n", + killer_sig == SIGINT ? "SIGINT" : "SIGTERM", killer_pid, killer_uid); +#if DESPERATE + __pmNotifyErr(LOG_INFO, "Try to find process in ps output ...\n"); + sprintf(buf, "sh -c \". \\$PCP_DIR/etc/pcp.env; ( \\$PCP_PS_PROG \\$PCP_PS_ALL_FLAGS | \\$PCP_AWK_PROG 'NR==1 {print} \\$2==%" FMT_PID " {print}' )\"", killer_pid); + system(buf); +#endif + } + else { + __pmNotifyErr(LOG_INFO, "pmcd caught %s from unknown process\n", + killer_sig == SIGINT ? "SIGINT" : "SIGTERM"); + } +#else + __pmNotifyErr(LOG_INFO, "pmcd caught %s\n", + killer_sig == SIGINT ? "SIGINT" : "SIGTERM"); +#endif + Shutdown(); + exit(0); +} + +static void +SignalRestart(void) +{ + time_t now; + + time(&now); + __pmNotifyErr(LOG_INFO, "\n\npmcd RESTARTED at %s", ctime(&now)); + fprintf(stderr, "\nCurrent PMCD clients ...\n"); + ShowClients(stderr); + ResetBadHosts(); + ParseRestartAgents(configFileName); +} + +static void +SignalReloadPMNS(void) +{ + int sts; + + /* Reload PMNS if necessary. + * Note: this will only stat() the base name i.e. ASCII pmns, + * typically $PCP_VAR_DIR/pmns/root and not $PCP_VAR_DIR/pmns/root.bin . + * This is considered a very low risk problem, as the binary + * PMNS is always compiled from the ASCII version; + * when one changes so should the other. + * This caveat was allowed to make the code a lot simpler. + */ + if (__pmHasPMNSFileChanged(pmnsfile)) { + __pmNotifyErr(LOG_INFO, "Reloading PMNS \"%s\"", + (pmnsfile==PM_NS_DEFAULT)?"DEFAULT":pmnsfile); + pmUnloadNameSpace(); + if (dupok) + sts = pmLoadASCIINameSpace(pmnsfile, 1); + else + sts = pmLoadNameSpace(pmnsfile); + if (sts < 0) { + __pmNotifyErr(LOG_ERR, "PMNS \"%s\" load failed: %s", + (pmnsfile == PM_NS_DEFAULT) ? "DEFAULT" : pmnsfile, + pmErrStr(sts)); + } + } + else { + __pmNotifyErr(LOG_INFO, "PMNS file \"%s\" is unchanged", + (pmnsfile == PM_NS_DEFAULT) ? "DEFAULT" : pmnsfile); + } +} + +/* Process I/O on file descriptors from agents that were marked as not ready + * to handle PDUs. + */ +static int +HandleReadyAgents(__pmFdSet *readyFds) +{ + int i, s, sts; + int fd; + int reason; + int ready = 0; + AgentInfo *ap; + __pmPDU *pb; + + for (i = 0; i < nAgents; i++) { + ap = &agent[i]; + if (ap->status.notReady) { + fd = ap->outFd; + if (__pmFD_ISSET(fd, readyFds)) { + int pinpdu; + + /* Expect an error PDU containing PM_ERR_PMDAREADY */ + reason = AT_COMM; /* most errors are protocol failures */ + pinpdu = sts = __pmGetPDU(ap->outFd, ANY_SIZE, _pmcd_timeout, &pb); + if (sts > 0) + pmcd_trace(TR_RECV_PDU, ap->outFd, sts, (int)((__psint_t)pb & 0xffffffff)); + if (sts == PDU_ERROR) { + s = __pmDecodeError(pb, &sts); + if (s < 0) { + sts = s; + pmcd_trace(TR_RECV_ERR, ap->outFd, PDU_ERROR, sts); + } + else { + /* sts is the status code from the error PDU */ + if (pmDebug && DBG_TRACE_APPL0) + __pmNotifyErr(LOG_INFO, + "%s agent (not ready) sent %s status(%d)\n", + ap->pmDomainLabel, + sts == PM_ERR_PMDAREADY ? + "ready" : "unknown", sts); + if (sts == PM_ERR_PMDAREADY) { + ap->status.notReady = 0; + sts = 1; + ready++; + } + else { + pmcd_trace(TR_RECV_ERR, ap->outFd, PDU_ERROR, sts); + sts = PM_ERR_IPC; + } + } + } + else { + if (sts < 0) + pmcd_trace(TR_RECV_ERR, ap->outFd, PDU_RESULT, sts); + else + pmcd_trace(TR_WRONG_PDU, ap->outFd, PDU_ERROR, sts); + sts = PM_ERR_IPC; /* Wrong PDU type */ + } + if (pinpdu > 0) + __pmUnpinPDUBuf(pb); + + if (ap->ipcType != AGENT_DSO && sts <= 0) + CleanupAgent(ap, reason, fd); + } + } + } + return ready; +} + +static void +CheckNewClient(__pmFdSet * fdset, int rfd, int family) +{ + int s, sts, accepted = 1; + __uint32_t challenge; + ClientInfo *cp; + + if (__pmFD_ISSET(rfd, fdset)) { + if ((cp = AcceptNewClient(rfd)) == NULL) + return; /* Accept failed and no client added */ + + sts = __pmAccAddClient(cp->addr, &cp->denyOps); +#if defined(HAVE_STRUCT_SOCKADDR_UN) + if (sts >= 0 && family == AF_UNIX) { + if ((sts = __pmServerSetLocalCreds(cp->fd, &cp->attrs)) < 0) { + __pmNotifyErr(LOG_ERR, + "ClientLoop: error extracting local credentials: %s", + pmErrStr(sts)); + } + } +#endif + if (sts >= 0) { + memset(&cp->pduInfo, 0, sizeof(cp->pduInfo)); + cp->pduInfo.version = PDU_VERSION; + cp->pduInfo.licensed = 1; + if (__pmServerHasFeature(PM_SERVER_FEATURE_SECURE)) + cp->pduInfo.features |= (PDU_FLAG_SECURE | PDU_FLAG_SECURE_ACK); + if (__pmServerHasFeature(PM_SERVER_FEATURE_COMPRESS)) + cp->pduInfo.features |= PDU_FLAG_COMPRESS; + if (__pmServerHasFeature(PM_SERVER_FEATURE_AUTH)) /* optionally */ + cp->pduInfo.features |= PDU_FLAG_AUTH; + if (__pmServerHasFeature(PM_SERVER_FEATURE_CREDS_REQD)) /* required */ + cp->pduInfo.features |= PDU_FLAG_CREDS_REQD; + challenge = *(__uint32_t *)(&cp->pduInfo); + sts = 0; + } + else { + challenge = 0; + accepted = 0; + } + + pmcd_trace(TR_XMIT_PDU, cp->fd, PDU_ERROR, sts); + + /* reset (no meaning, use fd table to version) */ + cp->pduInfo.version = UNKNOWN_VERSION; + + s = __pmSendXtendError(cp->fd, FROM_ANON, sts, htonl(challenge)); + if (s < 0) { + __pmNotifyErr(LOG_ERR, + "ClientLoop: error sending Conn ACK PDU to new client %s\n", + pmErrStr(s)); + if (sts >= 0) + /* + * prefer earlier failure status if any, else + * use the one from __pmSendXtendError() + */ + sts = s; + accepted = 0; + } + if (!accepted) + CleanupClient(cp, sts); + } +} + +/* Loop, synchronously processing requests from clients. */ + +static void +ClientLoop(void) +{ + int i, fd, sts; + int maxFd; + int checkAgents; + int reload_ns = 0; + __pmFdSet readableFds; + + for (;;) { + + /* Figure out which file descriptors to wait for input on. Keep + * track of the highest numbered descriptor for the select call. + */ + readableFds = clientFds; + maxFd = maxClientFd + 1; + + /* If an agent was not ready, it may send an ERROR PDU to indicate it + * is now ready. Add such agents to the list of file descriptors. + */ + checkAgents = 0; + for (i = 0; i < nAgents; i++) { + AgentInfo *ap = &agent[i]; + + if (ap->status.notReady) { + fd = ap->outFd; + __pmFD_SET(fd, &readableFds); + if (fd > maxFd) + maxFd = fd + 1; + checkAgents = 1; + if (pmDebug & DBG_TRACE_APPL0) + __pmNotifyErr(LOG_INFO, + "not ready: check %s agent on fd %d (max = %d)\n", + ap->pmDomainLabel, fd, maxFd); + } + } + + sts = __pmSelectRead(maxFd, &readableFds, NULL); + if (sts > 0) { + if (pmDebug & DBG_TRACE_APPL0) + for (i = 0; i <= maxClientFd; i++) + if (__pmFD_ISSET(i, &readableFds)) + fprintf(stderr, "DATA: from %s (fd %d)\n", + FdToString(i), i); + __pmServerAddNewClients(&readableFds, CheckNewClient); + if (checkAgents) + reload_ns = HandleReadyAgents(&readableFds); + HandleClientInput(&readableFds); + } + else if (sts == -1 && neterror() != EINTR) { + __pmNotifyErr(LOG_ERR, "ClientLoop select: %s\n", netstrerror()); + break; + } + if (restart) { + restart = 0; + reload_ns = 1; + SignalRestart(); + } + if (reload_ns) { + reload_ns = 0; + SignalReloadPMNS(); + } + if (timeToDie) { + SignalShutdown(); + break; + } + if (AgentDied) { + AgentDied = 0; + for (i = 0; i < nAgents; i++) { + if (!agent[i].status.connected) + mapdom[agent[i].pmDomainId] = nAgents; + } + } + } +} + +#ifdef HAVE_SA_SIGINFO +static void +SigIntProc(int sig, siginfo_t *sip, void *x) +{ + killer_sig = sig; + if (sip != NULL) { + killer_pid = sip->si_pid; + killer_uid = sip->si_uid; + } + timeToDie = 1; +} +#elif IS_MINGW +static void +SigIntProc(int sig) +{ + SignalShutdown(); +} +#else +static void +SigIntProc(int sig) +{ + killer_sig = sig; + signal(SIGINT, SigIntProc); + signal(SIGTERM, SigIntProc); + timeToDie = 1; +} +#endif + +#ifdef IS_MINGW +static void +SigHupProc(int sig) +{ + SignalRestart(); + SignalReloadPMNS(); +} +#else +static void +SigHupProc(int sig) +{ + signal(SIGHUP, SigHupProc); + restart = 1; +} +#endif + +static void +SigBad(int sig) +{ + if (pmDebug & DBG_TRACE_DESPERATE) { + __pmNotifyErr(LOG_ERR, "Unexpected signal %d ...\n", sig); + + /* -D desperate on the command line to enable traceback, + * if we have platform support for it + */ + fprintf(stderr, "\nProcedure call traceback ...\n"); + __pmDumpStack(stderr); + fflush(stderr); + } + _exit(sig); +} + +int +main(int argc, char *argv[]) +{ + int sts; + int nport = 0; + char *envstr; +#ifdef HAVE_SA_SIGINFO + static struct sigaction act; +#endif + + umask(022); + __pmProcessDataSize(NULL); + __pmGetUsername(&username); + __pmSetInternalState(PM_STATE_PMCS); + __pmServerSetFeature(PM_SERVER_FEATURE_DISCOVERY); + + if ((envstr = getenv("PMCD_PORT")) != NULL) + nport = __pmServerAddPorts(envstr); + ParseOptions(argc, argv, &nport); + if (nport == 0) + __pmServerAddPorts(TO_STRING(SERVER_PORT)); + + /* Set the local socket path. A message will be generated into the log + * if this fails, but it is not fatal, since other connection options + * may exist. + */ + __pmServerSetLocalSocket(sockpath); + + /* Set the service spec. This will cause our service to be advertised on + * the network if that is supported. + */ + __pmServerSetServiceSpec(PM_SERVER_SERVICE_SPEC); + + if (run_daemon) { + fflush(stderr); + StartDaemon(argc, argv); + } + +#ifdef HAVE_SA_SIGINFO + act.sa_sigaction = SigIntProc; + act.sa_flags = SA_SIGINFO; + sigaction(SIGINT, &act, NULL); + sigaction(SIGTERM, &act, NULL); +#else + __pmSetSignalHandler(SIGINT, SigIntProc); + __pmSetSignalHandler(SIGTERM, SigIntProc); +#endif + __pmSetSignalHandler(SIGHUP, SigHupProc); + __pmSetSignalHandler(SIGBUS, SigBad); + __pmSetSignalHandler(SIGSEGV, SigBad); + + if ((sts = __pmServerOpenRequestPorts(&clientFds, MAXPENDING)) < 0) + DontStart(); + maxReqPortFd = maxClientFd = sts; + + __pmOpenLog(pmProgname, logfile, stderr, &sts); + /* close old stdout, and force stdout into same stream as stderr */ + fflush(stdout); + close(fileno(stdout)); + sts = dup(fileno(stderr)); + /* if this fails beware of the sky falling in */ + assert(sts >= 0); + + if (dupok) + sts = pmLoadASCIINameSpace(pmnsfile, 1); + else + sts = pmLoadNameSpace(pmnsfile); + if (sts < 0) { + fprintf(stderr, "Error: pmLoadNameSpace: %s\n", pmErrStr(sts)); + DontStart(); + } + + if (ParseInitAgents(configFileName) < 0) { + /* error already reported in ParseInitAgents() */ + DontStart(); + } + + if (nAgents <= 0) { + fprintf(stderr, "Error: No PMDAs found in the configuration file \"%s\"\n", + configFileName); + DontStart(); + } + + if (run_daemon) { + if (__pmServerCreatePIDFile(PM_SERVER_SERVICE_SPEC, PM_FATAL_ERR) < 0) + DontStart(); + if (__pmSetProcessIdentity(username) < 0) + DontStart(); + } + + if (__pmSecureServerSetup(certdb, dbpassfile) < 0) + DontStart(); + + PrintAgentInfo(stderr); + __pmAccDumpLists(stderr); + fprintf(stderr, "\npmcd: PID = %" FMT_PID, getpid()); + fprintf(stderr, ", PDU version = %u\n", PDU_VERSION); + __pmServerDumpRequestPorts(stderr); + fflush(stderr); + + /* all the work is done here */ + ClientLoop(); + + Shutdown(); + exit(0); +} + +/* The bad host list is a list of IP addresses for hosts that have had clients + * cleaned up because of an access violation (permission or connection limit). + * This is used to ensure that the message printed in PMCD's log file when a + * client is terminated like this only appears once per host. That stops the + * log from growing too large if repeated access violations occur. + * The list is cleared when PMCD is reconfigured. + */ + +static int nBadHosts; +static int szBadHosts; +static __pmSockAddr **badHost; + +static int +AddBadHost(struct __pmSockAddr *hostId) +{ + int i, need; + + for (i = 0; i < nBadHosts; i++) + if (__pmSockAddrCompare(hostId, badHost[i]) == 0) + /* already there */ + return 0; + + /* allocate more entries if required */ + if (nBadHosts == szBadHosts) { + szBadHosts += 8; + need = szBadHosts * (int)sizeof(badHost[0]); + if ((badHost = (__pmSockAddr **)realloc(badHost, need)) == NULL) { + __pmNoMem("pmcd.AddBadHost", need, PM_FATAL_ERR); + } + } + badHost[nBadHosts++] = __pmSockAddrDup(hostId); + return 1; +} + +static void +ResetBadHosts(void) +{ + if (szBadHosts) { + while (nBadHosts > 0) { + --nBadHosts; + free (badHost[nBadHosts]); + } + free(badHost); + } + nBadHosts = 0; + szBadHosts = 0; + badHost = NULL; +} + +void +CleanupClient(ClientInfo *cp, int sts) +{ + char *caddr; + int i, msg; + int force; + + force = pmDebug & DBG_TRACE_APPL0; + + if (sts != 0 || force) { + /* for access violations, only print the message if this host hasn't + * been dinged for an access violation since startup or reconfiguration + */ + if (sts == PM_ERR_PERMISSION || sts == PM_ERR_CONNLIMIT) { + if ( (msg = AddBadHost(cp->addr)) ) { + caddr = __pmSockAddrToString(cp->addr); + fprintf(stderr, "access violation from host %s\n", caddr); + free(caddr); + } + } + else + msg = 0; + + if (msg || force) { + for (i = 0; i < nClients; i++) { + if (cp == &client[i]) + break; + } + fprintf(stderr, "endclient client[%d]: (fd %d) %s (%d)\n", + i, cp->fd, pmErrStr(sts), sts); + } + } + + /* If the client is being cleaned up because its connection was refused + * don't do this because it hasn't actually contributed to the connection + * count + */ + if (sts != PM_ERR_PERMISSION && sts != PM_ERR_CONNLIMIT) + __pmAccDelClient(cp->addr); + + pmcd_trace(TR_DEL_CLIENT, cp->fd, sts, 0); + DeleteClient(cp); + + if (maxClientFd < maxReqPortFd) + maxClientFd = maxReqPortFd; + + for (i = 0; i < nAgents; i++) + if (agent[i].profClient == cp) + agent[i].profClient = NULL; +} + +/* Convert a file descriptor to a string describing what it is for. */ +static char * +FdToString(int fd) +{ + static char fdStr[FDNAMELEN]; + static char *stdFds[4] = {"*UNKNOWN FD*", "stdin", "stdout", "stderr"}; + int i; + + if (fd >= -1 && fd < 3) + return stdFds[fd + 1]; + if (__pmServerRequestPortString(fd, fdStr, FDNAMELEN) != NULL) + return fdStr; + for (i = 0; i < nClients; i++) + if (client[i].status.connected) { + if (fd == client[i].fd) { + sprintf(fdStr, "client[%d] input socket", i); + return fdStr; + } + } + for (i = 0; i < nAgents; i++) + if (agent[i].status.connected) { + if (fd == agent[i].inFd) { + sprintf(fdStr, "agent[%d] input", i); + return fdStr; + } + else if (fd == agent[i].outFd) { + sprintf(fdStr, "agent[%d] output", i); + return fdStr; + } + } + return stdFds[0]; +} diff --git a/src/pmcd/src/pmcd.h b/src/pmcd/src/pmcd.h new file mode 100644 index 0000000..ba8be5e --- /dev/null +++ b/src/pmcd/src/pmcd.h @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2012-2013 Red Hat. + * Copyright (c) 1995-2001 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _PMCD_H +#define _PMCD_H + +#include "pmapi.h" +#include "impl.h" +#include "pmda.h" + +#ifdef IS_MINGW +#ifdef PMCD_INTERNAL +#define PMCD_INTERN __declspec(dllexport) +#define PMCD_EXTERN +#else +#define PMCD_INTERN +#define PMCD_EXTERN __declspec(dllimport) +#endif +#else /*!MINGW*/ +#define PMCD_INTERN +#define PMCD_EXTERN extern +#endif + +#include "client.h" + +/* Structures of type-specific info for each kind of domain agent-PMCD + * connection (DSO, socket, pipe). + */ + +typedef void (*DsoInitPtr)(pmdaInterface*); + +typedef struct { + char *pathName; /* Where the DSO lives */ + int xlatePath; /* translated pathname? */ + char *entryPoint; /* Name of the entry point */ + void *dlHandle; /* Handle for DSO */ + DsoInitPtr initFn; /* Function to initialise DSO */ + /* and return dispatch table */ + pmdaInterface dispatch; /* Dispatch table for dso agent */ +} DsoInfo; + +typedef struct { + int addrDomain; /* AF_UNIX, AF_INET or AF_INET6 */ + int port; /* Port number if an INET socket */ + char *name; /* Port name if supplied for INET */ + /* or socket name for UNIX */ + char *commandLine; /* Optional command to start agent */ + char* *argv; /* Arg list built from commandLine */ + pid_t agentPid; /* Process ID of agent if PMCD started */ +} SocketInfo; + +typedef struct { + char* commandLine; /* Command line to use for child */ + char* *argv; /* Arg list built from command line */ + pid_t agentPid; /* Process ID of the agent */ +} PipeInfo; + +/* The agent table and its size. */ + +typedef struct { + int pmDomainId; /* PMD identifier */ + int ipcType; /* DSO, socket or pipe */ + int pduVersion; /* PDU_VERSION for this agent */ + int inFd, outFd; /* For input to/output from agent */ + int done; /* Set when processed for this Fetch */ + ClientInfo *profClient; /* Last client to send profile to agent */ + int profIndex; /* Index of profile that client sent */ + char *pmDomainLabel; /* Textual label for agent's PMD */ + struct { /* Status of agent */ + unsigned int + connected : 1, /* Agent connected to pmcd */ + busy : 1, /* Processing a request */ + isChild : 1, /* Is a child process of pmcd */ + madeDsoResult : 1, /* Pmcd made a "bad" pmResult (DSO only) */ + restartKeep : 1, /* Keep agent if set during restart */ + notReady : 1, /* Agent not ready to process PDUs */ + startNotReady : 1, /* Agent starts in non-ready state */ + unused : 9, /* Zero-padded, unused space */ + flags : 16; /* Agent-supplied connection flags */ + } status; + int reason; /* if ! connected */ + union { /* per-ipcType info */ + DsoInfo dso; + SocketInfo socket; + PipeInfo pipe; + } ipc; +} AgentInfo; + +PMCD_EXTERN AgentInfo *agent; /* Array of domain agent structs */ +PMCD_EXTERN int nAgents; /* Number of agents in array */ + +/* + * DomainId-to-AgentIndex map + * 9 bits of DomainId, max value is 510 because 511 is special (see + * DYNAMIC_PMID in impl.h) + */ +#define MAXDOMID 510 +extern int mapdom[]; /* the map */ + +/* Domain agent-PMCD connection types (AgentInfo.ipcType) */ + +#define AGENT_DSO 0 +#define AGENT_SOCKET 1 +#define AGENT_PIPE 2 + +/* Masks for operations used in access controls for clients. */ +#define PMCD_OP_FETCH 0x1 +#define PMCD_OP_STORE 0x2 + +#define PMCD_OP_NONE 0x0 +#define PMCD_OP_ALL 0x3 + +/* Agent termination reasons */ +#define AT_CONFIG 1 +#define AT_COMM 2 +#define AT_EXIT 3 + +/* + * Agent termination reasons for "reason" in AgentInfo, and pmcd.agent.state + * as exported by PMCD PMDA ... these encode the low byte, next byte contains + * exit status and next byte encodes signal + */ + /* 0 connected */ + /* 1 connected, not ready */ +#define REASON_EXIT 2 +#define REASON_NOSTART 4 +#define REASON_PROTOCOL 8 + +extern AgentInfo *FindDomainAgent(int); +extern void CleanupAgent(AgentInfo *, int, int); +extern int HarvestAgents(unsigned int); + +/* timeout to PMDAs (secs) */ +PMCD_EXTERN int _pmcd_timeout; + +/* timeout for credentials */ +extern int _creds_timeout; + +/* global PMCD PMDA variables */ + +/* + * trace types + */ + +#define TR_ADD_CLIENT 1 +#define TR_DEL_CLIENT 2 +#define TR_ADD_AGENT 3 +#define TR_DEL_AGENT 4 +#define TR_EOF 5 +#define TR_XMIT_PDU 7 +#define TR_RECV_PDU 8 +#define TR_WRONG_PDU 9 +#define TR_XMIT_ERR 10 +#define TR_RECV_TIMEOUT 11 +#define TR_RECV_ERR 12 + +/* + * trace control + */ +PMCD_EXTERN int _pmcd_trace_mask; +PMCD_EXTERN int _pmcd_trace_nbufs; + +/* + * trace mask bits + */ +#define TR_MASK_CONN 1 +#define TR_MASK_PDU 2 +#define TR_MASK_NOBUF 256 + +/* + * routines + */ +extern void pmcd_init_trace(int); +extern void pmcd_trace(int, int, int, int); +extern void pmcd_dump_trace(FILE *); +extern int pmcd_load_libpcp_pmda(void); + +/* + * PDU handling routines + */ +extern int DoFetch(ClientInfo *, __pmPDU *); +extern int DoProfile(ClientInfo *, __pmPDU *); +extern int DoDesc(ClientInfo *, __pmPDU *); +extern int DoInstance(ClientInfo *, __pmPDU *); +extern int DoText(ClientInfo *, __pmPDU *); +extern int DoStore(ClientInfo *, __pmPDU *); +extern int DoCreds(ClientInfo *, __pmPDU *); +extern int DoPMNSIDs(ClientInfo *, __pmPDU *); +extern int DoPMNSNames(ClientInfo *, __pmPDU *); +extern int DoPMNSChild(ClientInfo *, __pmPDU *); +extern int DoPMNSTraverse(ClientInfo *, __pmPDU *); + +/* + * General purpose routines + */ +extern void StartDaemon(int, char **); +extern void Shutdown(void); +extern int ParseInitAgents(char *); +extern void ParseRestartAgents(char *); +extern void PrintAgentInfo(FILE *); +extern void MarkStateChanges(int); +extern void CleanupClient(ClientInfo *, int); +extern int ClientsAuthentication(AgentInfo *); +extern int AgentsAuthentication(int); +extern pmResult **SplitResult(pmResult *); + +/* + * Highest known file descriptor used for a Client or an Agent connection. + * This is reported in the pmcd.openfds metric. + */ +PMCD_EXTERN int pmcd_hi_openfds; +extern void pmcd_openfds_sethi(int fd); + +/* Explicitly requested hostname (pmcd.hostname metric) */ +PMCD_EXTERN char *_pmcd_hostname; + +#endif /* _PMCD_H */ diff --git a/src/pmcd/src/util.c b/src/pmcd/src/util.c new file mode 100644 index 0000000..094d400 --- /dev/null +++ b/src/pmcd/src/util.c @@ -0,0 +1,96 @@ +/* + * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved. + * Copyright (c) 2009 Aconex. 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. + */ + +#include "pmcd.h" + +#ifdef IS_MINGW + +void +StartDaemon(int argc, char **argv) +{ + PROCESS_INFORMATION piProcInfo; + STARTUPINFO siStartInfo; + LPTSTR cmdline = NULL; + int i, sz = 3; /* -f\0 */ + + for (i = 0; i < argc; i++) + sz += strlen(argv[i]) + 1; + if ((cmdline = malloc(sz)) == NULL) { + __pmNotifyErr(LOG_ERR, "StartDaemon: no memory"); + exit(1); + } + for (sz = i = 0; i < argc; i++) + sz += sprintf(cmdline, "%s ", argv[i]); + sprintf(cmdline + sz, "-f"); + + ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION)); + ZeroMemory(&siStartInfo, sizeof(STARTUPINFO)); + siStartInfo.cb = sizeof(STARTUPINFO); + + if (0 == CreateProcess( + NULL, cmdline, + NULL, NULL, /* process and thread attributes */ + FALSE, /* inherit handles */ + CREATE_NEW_PROCESS_GROUP | CREATE_NO_WINDOW | DETACHED_PROCESS, + NULL, /* environment (from parent) */ + NULL, /* current directory */ + &siStartInfo, /* STARTUPINFO pointer */ + &piProcInfo)) { /* receives PROCESS_INFORMATION */ + __pmNotifyErr(LOG_ERR, "StartDaemon: CreateProcess"); + /* but keep going */ + } + else { + /* parent, let her exit, but avoid ugly "Log finished" messages */ + fclose(stderr); + exit(0); + } +} + +#else + +/* Based on Stevens (Unix Network Programming, p.83) */ +void +StartDaemon(int argc, char **argv) +{ + pid_t childpid; + + (void)argc; (void)argv; + +#if defined(HAVE_TERMIO_SIGNALS) + signal(SIGTTOU, SIG_IGN); + signal(SIGTTIN, SIG_IGN); + signal(SIGTSTP, SIG_IGN); +#endif + + if ((childpid = fork()) < 0) + __pmNotifyErr(LOG_ERR, "StartDaemon: fork"); + /* but keep going */ + else if (childpid > 0) { + /* parent, let her exit, but avoid ugly "Log finished" messages */ + fclose(stderr); + exit(0); + } + + /* not a process group leader, lose controlling tty */ + if (setsid() == -1) + __pmNotifyErr(LOG_WARNING, "StartDaemon: setsid"); + /* but keep going */ + + close(0); + /* don't close other fd's -- we know that only good ones are open! */ + + /* don't chdir("/") -- we still need to open pmcd.log */ +} +#endif |