summaryrefslogtreecommitdiff
path: root/src/pmcd
diff options
context:
space:
mode:
Diffstat (limited to 'src/pmcd')
-rw-r--r--src/pmcd/GNUmakefile52
-rw-r--r--src/pmcd/pmcd.options36
-rw-r--r--src/pmcd/pmcd.service14
-rw-r--r--src/pmcd/pmcd.service.in14
-rw-r--r--src/pmcd/pmdaproc.sh1457
-rw-r--r--src/pmcd/rc-proc.sh394
-rw-r--r--src/pmcd/rc-proc.sh.minimal74
-rw-r--r--src/pmcd/rc_local67
-rw-r--r--src/pmcd/rc_pcp76
-rw-r--r--src/pmcd/rc_pmcd540
-rw-r--r--src/pmcd/sasl2.conf19
-rw-r--r--src/pmcd/src/GNUmakefile38
-rw-r--r--src/pmcd/src/agent.c221
-rw-r--r--src/pmcd/src/client.c265
-rw-r--r--src/pmcd/src/client.h58
-rw-r--r--src/pmcd/src/config.c2526
-rw-r--r--src/pmcd/src/dofetch.c582
-rw-r--r--src/pmcd/src/dopdus.c1057
-rw-r--r--src/pmcd/src/dostore.c312
-rw-r--r--src/pmcd/src/pmcd.c1024
-rw-r--r--src/pmcd/src/pmcd.h233
-rw-r--r--src/pmcd/src/util.c96
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