Merge branch 'collectd-4.3' into collectd-4.4
authorFlorian Forster <octo@huhu.verplant.org>
Wed, 27 Aug 2008 08:12:55 +0000 (10:12 +0200)
committerFlorian Forster <octo@huhu.verplant.org>
Wed, 27 Aug 2008 08:12:55 +0000 (10:12 +0200)
91 files changed:
.gitignore
AUTHORS
ChangeLog
README
TODO
bindings/perl/Collectd.pm
bindings/perl/Collectd/Unixsock.pm
configure.in
contrib/collection3/README [new file with mode: 0644]
contrib/collection3/bin/graph.cgi [new file with mode: 0755]
contrib/collection3/bin/index.cgi [new file with mode: 0755]
contrib/collection3/bin/json.cgi [new file with mode: 0755]
contrib/collection3/etc/.htaccess [new file with mode: 0644]
contrib/collection3/etc/collection.conf [new file with mode: 0644]
contrib/collection3/lib/.htaccess [new file with mode: 0644]
contrib/collection3/lib/Collectd/Config.pm [new file with mode: 0644]
contrib/collection3/lib/Collectd/Graph/Common.pm [new file with mode: 0644]
contrib/collection3/lib/Collectd/Graph/Config.pm [new file with mode: 0644]
contrib/collection3/lib/Collectd/Graph/Type.pm [new file with mode: 0644]
contrib/collection3/lib/Collectd/Graph/Type/Df.pm [new file with mode: 0644]
contrib/collection3/lib/Collectd/Graph/Type/GenericIO.pm [new file with mode: 0644]
contrib/collection3/lib/Collectd/Graph/Type/GenericStacked.pm [new file with mode: 0644]
contrib/collection3/lib/Collectd/Graph/Type/Load.pm [new file with mode: 0644]
contrib/collection3/lib/Collectd/Graph/TypeLoader.pm [new file with mode: 0644]
contrib/collection3/share/.htaccess [new file with mode: 0644]
contrib/collection3/share/navigate.js [new file with mode: 0644]
contrib/collection3/share/shortcut-icon.png [new file with mode: 0644]
contrib/collection3/share/style.css [new file with mode: 0644]
contrib/cussh.pl
contrib/migrate-3-4.px
src/Makefile.am
src/apache.c
src/ascent.c [new file with mode: 0644]
src/collectd-exec.pod
src/collectd-nagios.c
src/collectd-perl.pod
src/collectd-unixsock.pod
src/collectd.c
src/collectd.conf.in
src/collectd.conf.pod
src/collectd.pod
src/cpu.c
src/disk.c
src/email.c
src/exec.c
src/ipmi.c [new file with mode: 0644]
src/iptables.c
src/libiptc/Makefile.am [new file with mode: 0644]
src/libiptc/ipt_kernel_headers.h [new file with mode: 0644]
src/libiptc/libip4tc.c [new file with mode: 0644]
src/libiptc/libip6tc.c [new file with mode: 0644]
src/libiptc/libip6tc.h [new file with mode: 0644]
src/libiptc/libiptc.c [new file with mode: 0644]
src/libiptc/libiptc.h [new file with mode: 0644]
src/libiptc/linux_list.h [new file with mode: 0644]
src/network.c
src/nginx.c
src/perl.c
src/plugin.c
src/plugin.h
src/powerdns.c [new file with mode: 0644]
src/rrdtool.c
src/sensors.c
src/tail.c [new file with mode: 0644]
src/teamspeak2.c [new file with mode: 0644]
src/types.db
src/types_list.c
src/unixsock.c
src/users.c
src/utils_cache.c
src/utils_cache.h
src/utils_cmd_flush.c [new file with mode: 0644]
src/utils_cmd_flush.h [new file with mode: 0644]
src/utils_cmd_getval.c [new file with mode: 0644]
src/utils_cmd_getval.h [new file with mode: 0644]
src/utils_cmd_listval.c [new file with mode: 0644]
src/utils_cmd_listval.h [new file with mode: 0644]
src/utils_cmd_putnotif.c
src/utils_cmd_putnotif.h
src/utils_cmd_putval.c
src/utils_cmd_putval.h
src/utils_ignorelist.c
src/utils_match.c [new file with mode: 0644]
src/utils_match.h [new file with mode: 0644]
src/utils_tail.c [new file with mode: 0644]
src/utils_tail.h [new file with mode: 0644]
src/utils_tail_match.c [new file with mode: 0644]
src/utils_tail_match.h [new file with mode: 0644]
src/utils_threshold.c
src/vmem.c [new file with mode: 0644]
version-gen.sh

index 4663001..03bd0c4 100644 (file)
@@ -50,6 +50,9 @@ src/collectd*.1
 src/collectd*.5
 src/types.db.5
 src/config.h.in~
+src/libiptc/.libs
+src/libiptc/*.la
+src/libiptc/*.lo
 src/liboconfig/.libs
 src/liboconfig/*.la
 src/liboconfig/*.lo
diff --git a/AUTHORS b/AUTHORS
index bd5daa4..7f92d55 100644 (file)
--- a/AUTHORS
+++ b/AUTHORS
@@ -4,7 +4,8 @@ This package was written by:
 apcups plugin by:
   Anthony Gialluca <tonyabg at charter.net>
 
-cpufreq, multimeter and irq plugin by:
+cpufreq, multimeter and irq plugin, as well as some bugfixes in the exec plugin
+by:
   Peter Holik <peter at holik.at>
 
 hddtemp plugin by:
@@ -28,6 +29,9 @@ nfs plugin by:
 perl plugin by:
   Sebastian Harl <sh at tokkee.org>
 
+powerdns plugin and initial `tail' subsystem by:
+  Luke Herberling <collectd at c-ware.com>
+
 processes plugin by:
   Lyonel Vincent <lyonel at ezix.org>
 
@@ -40,12 +44,15 @@ serial plugin by:
 tape plugin by:
   Scott Garrett <sgarrett at technomancer.com>
 
+teamspeak2 plugin by:
+  Stefan Hacker <stefan.hacker at web.de>
+
 users plugin by:
   Sebastian Harl <sh at tokkee.org>
 
 uuid plugin by:
-  Dan Berrange <berrange@redhat.com>
-  Richard W.M. Jones <rjones@redhat.com>
+  Dan Berrange <berrange at redhat.com>
+  Richard W.M. Jones <rjones at redhat.com>
 
 vserver plugin by:
   Sebastian Harl <sh at tokkee.org>
@@ -67,6 +74,10 @@ Much time and effort to find a nasty bug in the ntpd-plugin has been
 contributed by:
   LuboÅ¡ StanÄ›k <lubek at users.sourceforge.net>
 
+Support for the statgrab library has been added to the cpu, disk and users
+plugins by
+  Oleg King <king2 at kaluga.ru>
+
 collectd is available at:
   <http://collectd.org/>
 
index 8b074b4..35e8b36 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,67 @@
+2008-07-15, Version 4.4.2
+       * build system: Use pkg-config to detect the upsclient library.
+       * collectd: Try even harder to determine the endianess of the
+         architecture collectd is being built on.
+       * disk plugin: Fix for Linux 2.4: A wrong field was used as the name
+         of disks.
+       * dns plugin: Fix compilation errors with BIND versions 19991001
+         through 19991005.
+       * network plugin: Bugfix in the init routine: The init function
+         cleared a buffer regardless of its contents. This could lead to lost
+         values under Solaris.
+       * nginx plugin: Remove usage of the thread-unsafe `strtok' function.
+       * vserver plugin: Remove usage of the thread-unsafe `readdir'
+         function.
+       * wireless plugin: Work around incorrect noise and power values
+         returned by some broken drivers.
+
+2008-06-03, Version 4.4.1
+       * collectd: Fix the `DataSource' option within `Type' blocks. Thanks
+         to kyrone for reporting this.
+       * collectd: Fixed min/max output in notifications generated by
+         threshold checking.
+       * collectd-nagios: Fix the protocol used to communicate with the
+         daemon.
+       * perl plugin: Fail noisily, but don't shutdown the daemon, if
+         initialization has errors. An issue with Perl 5.10 has been fixed.
+       * teamspeak2 plugin: Fixed an out of bounce array access. Thanks to
+         René Rebe and Siegmund Gorr for reporting this.
+
+2008-05-06, Version 4.4.0
+       * collectd: Internal code cleanups.
+       * collectd: Added support for a `Flush' command in the unixsock and
+         exec plugins. This command can be used to force a plugin (or all) to
+         flush its values to disk.
+       * collectd: Thresholds can now be configured to apply to one data
+         source only, making it possible to configure different thresholds
+         for each data source.
+       * apache, nginx plugins: Added the possibility to disable host and/or
+         peer verification.
+       * ascent plugin: The new ascent plugin reads and parses the statistics
+         page of an Ascent server.
+       * cpu plugin: Support for the statgrab library has been added.
+       * disk plugin: The possibility to ignore certain disks or collect only
+         specific disks has been added.
+       * disk plugin: Support for the statgrab library has been added.
+       * ipmi plugin: The new ipmi plugin uses the OpenIPMI library to read
+         sensor values via IPMI, the intelligent platform management
+         interface.
+       * iptables plugin: The iptc library that is used by the iptables
+         plugin has been added to the distribution, because it is not
+         provided by all distributions and removed from at least one.
+       * powerdns plugin: The new powerdns plugin reads statistics from an
+         authoritative or a recursing PowerDNS name server.
+       * rrdtool plugin: The size of the files generated with the default
+         configuration has been decreased.
+       * tail plugin: The new tail plugin can be used to gather statistics by
+         continuously reading from log files.
+       * teamspeak2 plugin: The new teamspeak2 plugin connects to a
+         TeamSpeak2 server and collects statistics about the number of users
+         and number of channels.
+       * users plugin: Support for the statgrab library has been added.
+       * vmem plugin: The new vmem plugin collects very detailed statistics
+         about the virtual memory subsystem of Linux.
+
 2008-04-22, Version 4.3.3
        * build system: Improved detection of several libraries, especially if
          they are in non-standard paths.
          the data into text files rather than RRD files.
 
 2006-04-09, Version 3.8.4
-       * Applied patch by Vincent Stehlé which improves the disk-name
+       * Applied patch by Vincent Stehlé which improves the disk-name
          resolution in the `hddtemp' plugin for Linux systems.
 
 2006-04-02, Version 3.8.3
 
 2005-10-16, Version 3.1.0 (Revision 194)
        * Added the `setsid' syscall to the startup code.
-       * Support for hddtemp has been added (thanks to Vincent Stehlé)
+       * Support for hddtemp has been added (thanks to Vincent Stehlé)
 
 2005-09-30, Version 3.0.0 (Revision 184)
        * The ability to send/receive data to/from the network (think
diff --git a/README b/README
index 8c0be1d..904e7c7 100644 (file)
--- a/README
+++ b/README
@@ -26,6 +26,9 @@ Features
       Sensors in Macs running Mac OS X / Darwin: Temperature, fanspeed and
       voltage sensors.
 
+    - ascent
+      Statistics about Ascent, a free server for the game `World of Warcraft'.
+
     - battery
       Batterycharge, -current and voltage of ACPI and PMU based laptop
       batteries.
@@ -156,6 +159,10 @@ Features
     - swap
       Pages swapped out onto harddisk or whatever is called `swap' by the OS..
 
+    - tail
+      Follows (tails) logfiles, parses them by lines and submits matched
+      values.
+
     - tape
       Bytes and operations read and written on tape devices. Solaris only.
 
@@ -165,6 +172,10 @@ Features
     - users
       Users currently logged in.
 
+    - vmem
+      Virtual memory statistics, e. g. the number of page-ins/-outs or the
+      number of pagefaults.
+
     - vserver
       System resources used by Linux VServers.
       See <http://linux-vserver.org/>.
@@ -305,7 +316,7 @@ Prerequisites
     platforms.
 
   * libcurl (optional)
-    If you want to use the `apache' and/or `nginx' plugins.
+    If you want to use the `apache', `ascent', or `nginx' plugin.
 
   * libhal (optional)
     If present, the uuid plugin will check for UUID from HAL.
@@ -363,7 +374,7 @@ Prerequisites
     Collect statistics from virtual machines.
 
   * libxml2 (optional)
-    Parse XML data provided by libvirt.
+    Parse XML data. This is needed for the `ascent' and `libvirt' plugins.
 
 
 Configuring / Compiling / Installing
diff --git a/TODO b/TODO
index 2a5b7ca..08fcec1 100644 (file)
--- a/TODO
+++ b/TODO
@@ -1,3 +1,6 @@
+For version 4.4:
+* PowerDNS plugin
+
 For version 4.3:
 * unixsock plugin: Remove the custom cache if possible.
 
index 4ba9751..cca349e 100644 (file)
@@ -1,5 +1,5 @@
 # collectd - Collectd.pm
-# Copyright (C) 2007  Sebastian Harl
+# Copyright (C) 2007, 2008  Sebastian Harl
 #
 # 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
@@ -42,6 +42,9 @@ our %EXPORT_TAGS = (
                        plugin_register
                        plugin_unregister
                        plugin_dispatch_values
+                       plugin_flush
+                       plugin_flush_one
+                       plugin_flush_all
                        plugin_dispatch_notification
                        plugin_log
        ) ],
@@ -52,6 +55,7 @@ our %EXPORT_TAGS = (
                        TYPE_SHUTDOWN
                        TYPE_LOG
                        TYPE_NOTIF
+                       TYPE_FLUSH
                        TYPE_DATASET
        ) ],
        'ds_types' => [ qw(
@@ -101,7 +105,8 @@ my %types = (
        TYPE_WRITE,    "write",
        TYPE_SHUTDOWN, "shutdown",
        TYPE_LOG,      "log",
-       TYPE_NOTIF,    "notify"
+       TYPE_NOTIF,    "notify",
+       TYPE_FLUSH,    "flush"
 );
 
 foreach my $type (keys %types) {
@@ -290,6 +295,34 @@ sub plugin_unregister {
        }
 }
 
+sub plugin_flush {
+       my %args = @_;
+
+       my $timeout = -1;
+
+       DEBUG ("Collectd::plugin_flush:"
+               . (defined ($args{'timeout'}) ? " timeout = $args{'timeout'}" : "")
+               . (defined ($args{'plugins'}) ? " plugins = $args{'plugins'}" : ""));
+
+       if (defined ($args{'timeout'}) && ($args{'timeout'} > 0)) {
+               $timeout = $args{'timeout'};
+       }
+
+       if (! defined $args{'plugins'}) {
+               plugin_flush_all ($timeout);
+       }
+       else {
+               if ("ARRAY" eq ref ($args{'plugins'})) {
+                       foreach my $plugin (@{$args{'plugins'}}) {
+                               plugin_flush_one ($timeout, $plugin);
+                       }
+               }
+               else {
+                       plugin_flush_one ($timeout, $args{'plugins'});
+               }
+       }
+}
+
 1;
 
 # vim: set sw=4 ts=4 tw=78 noexpandtab :
index 71a6d09..29fac3e 100644 (file)
@@ -406,6 +406,63 @@ sub putnotif
        return;
 } # putnotif
 
+=item I<$obj>-E<gt>B<flush> (B<timeout> =E<gt> I<$timeout>, B<plugins> =E<gt> [...]);
+
+Flush cached data.
+
+Valid options are:
+
+=over 4
+
+=item B<timeout>
+
+If this option is specified, only data older than I<$timeout> seconds is
+flushed.
+
+=item B<plugins>
+
+If this option is specified, only the selected plugins will be flushed. 
+
+=back
+
+=cut
+
+sub flush
+{
+       my $obj  = shift;
+       my %args = @_;
+
+       my $fh = $obj->{'sock'} or confess;
+
+       my $status = 0;
+       my $msg    = "FLUSH";
+
+       if ($args{'timeout'})
+       {
+               $msg .= " timeout=" . $args{'timeout'};
+       }
+
+       if ($args{'plugins'})
+       {
+               foreach my $plugin (@{$args{'plugins'}})
+               {
+                       $msg .= " plugin=" . $plugin;
+               }
+       }
+
+       $msg .= "\n";
+
+       send ($fh, $msg, 0) or confess ("send: $!");
+       $msg = undef;
+       recv ($fh, $msg, 1024, 0) or confess ("recv: $!");
+
+       ($status, $msg) = split (' ', $msg, 2);
+       return (1) if ($status == 0);
+
+       $obj->{'error'} = $msg;
+       return;
+}
+
 =item I<$obj>-E<gt>destroy ();
 
 Closes the socket before the object is destroyed. This function is also
index 17d006d..8ca5d69 100644 (file)
@@ -50,6 +50,18 @@ case $host_os in
 esac
 AC_MSG_RESULT([$ac_system])
 
+if test "x$ac_system" = "xLinux"
+then
+       AC_ARG_VAR([KERNEL_DIR], [path to Linux kernel sources])
+       if test -z "$KERNEL_DIR"
+       then
+               KERNEL_DIR="/lib/modules/`uname -r`/source"
+       fi
+
+       KERNEL_CFLAGS="-I$KERNEL_DIR/include"
+       AC_SUBST(KERNEL_CFLAGS)
+fi
+
 #
 # Checks for header files.
 #
@@ -278,8 +290,13 @@ have_net_ip_vs_h="no"
 have_ip_vs_h="no"
 if test "x$ac_system" = "xLinux"
 then
+       SAVE_CFLAGS=$CFLAGS
+       CFLAGS="$CFLAGS $KERNEL_CFLAGS"
+
        AC_CHECK_HEADERS(net/ip_vs.h, [have_net_ip_vs_h="yes"])
        AC_CHECK_HEADERS(ip_vs.h, [have_ip_vs_h="yes"])
+
+       CFLAGS=$SAVE_CFLAGS
 fi
 
 # For quota module
@@ -1602,6 +1619,7 @@ then
        LDFLAGS=$SAVE_LDFLAGS
 fi
 
+with_own_libiptc="no"
 AC_ARG_WITH(libiptc, [AS_HELP_STRING([--with-libiptc@<:@=PREFIX@:>@], [Path to libiptc.])],
 [
        if test "x$withval" != "xno" && test "x$withval" != "xyes"
@@ -1626,22 +1644,45 @@ then
        AC_CHECK_LIB(iptc, iptc_init,
        [
                AC_DEFINE(HAVE_LIBIPTC, 1, [Define to 1 if you have the iptc library (-liptc).])
-       ], [with_libiptc="no (libiptc not found)"])
+       ],
+       [
+               with_libiptc="yes"
+               with_own_libiptc="yes"
+       ])
 fi
-if test "x$with_libiptc" = "xyes"
+if test "x$with_libiptc" = "xyes" -a "x$with_own_libiptc" != "xyes"
 then
        AC_CHECK_HEADERS(libiptc/libiptc.h,
        [
                AC_DEFINE(HAVE_LIBIPTC_LIBIPTC_H, 1, [Define to 1 if you have the <libiptc/libiptc.h> header file.])
-       ], [with_libiptc="no (libiptc/libiptc.h not found)"])
+       ],
+       [
+               with_libiptc="yes"
+               with_own_libiptc="yes"
+       ])
 fi
 if test "x$with_libiptc" = "xyes"
 then
-       collect_libiptc=1
-else
-       collect_libiptc=0
+       SAVE_CFLAGS=$CFLAGS
+       CFLAGS="$CFLAGS $KERNEL_CFLAGS"
+
+       AC_CHECK_HEADERS(linux/netfilter_ipv4/ip_tables.h linux/netfilter_ipv6/ip6_tables.h, [],
+       [
+               with_libiptc="no (Linux iptables headers not found - check KERNEL_DIR)"
+               with_own_libiptc="no"
+       ],
+       [
+#include "$srcdir/src/libiptc/ipt_kernel_headers.h"
+       ])
+
+       CFLAGS=$SAVE_CFLAGS
 fi
 AM_CONDITIONAL(BUILD_WITH_LIBIPTC, test "x$with_libiptc" = "xyes")
+AM_CONDITIONAL(BUILD_WITH_OWN_LIBIPTC, test "x$with_own_libiptc" = "xyes")
+if test "x$with_own_libiptc" = "xyes"
+then
+       AC_DEFINE(OWN_LIBIPTC, 1, [Define to 1 if we use the shipped iptc library.])
+fi
 
 with_snmp_config="net-snmp-config"
 with_snmp_cflags=""
@@ -1951,6 +1992,85 @@ then
 fi
 AM_CONDITIONAL(BUILD_WITH_LIBNETLINK, test "x$with_libnetlink" = "xyes")
 
+with_libopenipmipthread="yes"
+with_libopenipmipthread_cflags=""
+with_libopenipmipthread_libs=""
+
+AC_MSG_CHECKING([for pkg-config])
+temp_result="no"
+if test "x$PKG_CONFIG" = "x"
+then
+       with_libopenipmipthread="no"
+       temp_result="no"
+else
+       temp_result="$PKG_CONFIG"
+fi
+AC_MSG_RESULT([$temp_result])
+
+if test "x$with_libopenipmipthread" = "xyes"
+then
+       AC_MSG_CHECKING([for libOpenIPMIpthread])
+       $PKG_CONFIG --exists OpenIPMIpthread 2>/dev/null
+       if test "$?" != "0"
+       then
+               with_libopenipmipthread="no ($PKG_CONFIG doesn't know OpenIPMIpthread)"
+       fi
+       AC_MSG_RESULT([$with_libopenipmipthread])
+fi
+
+if test "x$with_libopenipmipthread" = "xyes"
+then
+       AC_MSG_CHECKING([for libOpenIPMIpthread CFLAGS])
+       temp_result="`$PKG_CONFIG --cflags OpenIPMIpthread`"
+       if test "$?" = "0"
+       then
+               with_libopenipmipthread_cflags="$temp_result"
+       else
+               with_libopenipmipthread="no ($PKG_CONFIG --cflags OpenIPMIpthread failed)"
+               temp_result="$PKG_CONFIG --cflags OpenIPMIpthread failed"
+       fi
+       AC_MSG_RESULT([$temp_result])
+fi
+
+if test "x$with_libopenipmipthread" = "xyes"
+then
+       AC_MSG_CHECKING([for libOpenIPMIpthread LDFLAGS])
+       temp_result="`$PKG_CONFIG --libs OpenIPMIpthread`"
+       if test "$?" = "0"
+       then
+               with_libopenipmipthread_ldflags="$temp_result"
+       else
+               with_libopenipmipthread="no ($PKG_CONFIG --libs OpenIPMIpthread failed)"
+               temp_result="$PKG_CONFIG --libs OpenIPMIpthread failed"
+       fi
+       AC_MSG_RESULT([$temp_result])
+fi
+
+if test "x$with_libopenipmipthread" = "xyes"
+then
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       CPPFLAGS="$CPPFLAGS $with_libopenipmipthread_cflags"
+
+       AC_CHECK_HEADERS(OpenIPMI/ipmi_smi.h,
+                        [with_libopenipmipthread="yes"],
+                        [with_libopenipmipthread="no (OpenIPMI/ipmi_smi.h not found)"],
+[#include <OpenIPMI/ipmiif.h>
+#include <OpenIPMI/ipmi_err.h>
+#include <OpenIPMI/ipmi_posix.h>
+#include <OpenIPMI/ipmi_conn.h>
+])
+
+       CPPFLAGS="$SAVE_CPPFLAGS"
+fi
+
+if test "x$with_libopenipmipthread" = "xyes"
+then
+       BUILD_WITH_OPENIPMI_CFLAGS="$with_libopenipmipthread_cflags"
+       BUILD_WITH_OPENIPMI_LIBS="$with_libopenipmipthread_ldflags"
+       AC_SUBST(BUILD_WITH_OPENIPMI_CFLAGS)
+       AC_SUBST(BUILD_WITH_OPENIPMI_LIBS)
+fi
+
 dnl Check for libvirt and libxml2 libraries.
 with_libxml2="no (pkg-config isn't available)"
 with_libxml2_cflags=""
@@ -2172,6 +2292,7 @@ AC_COLLECTD([debug],     [enable],  [feature], [debugging])
 AC_COLLECTD([daemon],    [disable], [feature], [daemon mode])
 AC_COLLECTD([getifaddrs],[enable],  [feature], [getifaddrs under Linux])
 
+plugin_ascent="no"
 plugin_battery="no"
 plugin_cpu="no"
 plugin_cpufreq="no"
@@ -2179,6 +2300,7 @@ plugin_df="no"
 plugin_disk="no"
 plugin_entropy="no"
 plugin_interface="no"
+plugin_ipmi="no"
 plugin_ipvs="no"
 plugin_irq="no"
 plugin_libvirt="no"
@@ -2193,6 +2315,7 @@ plugin_swap="no"
 plugin_tape="no"
 plugin_tcpconns="no"
 plugin_users="no"
+plugin_vmem="no"
 plugin_vserver="no"
 plugin_wireless="no"
 
@@ -2213,6 +2336,7 @@ then
        plugin_serial="yes"
        plugin_swap="yes"
        plugin_tcpconns="yes"
+       plugin_vmem="yes"
        plugin_vserver="yes"
        plugin_wireless="yes"
 
@@ -2243,10 +2367,23 @@ fi
 # libstatgrab
 if test "x$with_libstatgrab" = "xyes"
 then
+       plugin_cpu="yes"
+       plugin_disk="yes"
        plugin_interface="yes"
        plugin_load="yes"
        plugin_memory="yes"
        plugin_swap="yes"
+       plugin_users="yes"
+fi
+
+if test "x$with_libcurl" = "xyes" && test "x$with_libxml2" = "xyes"
+then
+       plugin_ascent="yes"
+fi
+
+if test "x$with_libopenipmipthread" = "xyes"
+then
+       plugin_ipmi="yes"
 fi
 
 if test "x$have_processor_info" = "xyes"
@@ -2327,6 +2464,7 @@ collectd plugins:])
 AC_PLUGIN([apache],      [$with_libcurl],      [Apache httpd statistics])
 AC_PLUGIN([apcups],      [yes],                [Statistics of UPSes by APC])
 AC_PLUGIN([apple_sensors], [$with_libiokit],   [Apple's hardware sensors])
+AC_PLUGIN([ascent],      [$plugin_ascent],     [AscentEmu player statistics])
 AC_PLUGIN([battery],     [$plugin_battery],    [Battery statistics])
 AC_PLUGIN([cpu],         [$plugin_cpu],        [CPU usage statistics])
 AC_PLUGIN([cpufreq],     [$plugin_cpufreq],    [CPU frequency statistics])
@@ -2340,6 +2478,7 @@ AC_PLUGIN([exec],        [yes],                [Execution of external programs])
 AC_PLUGIN([hddtemp],     [yes],                [Query hddtempd])
 AC_PLUGIN([interface],   [$plugin_interface],  [Interface traffic statistics])
 AC_PLUGIN([iptables],    [$with_libiptc],      [IPTables rule counters])
+AC_PLUGIN([ipmi],        [$plugin_ipmi],       [IPMI sensor statistics])
 AC_PLUGIN([ipvs],        [$plugin_ipvs],       [IPVS connection statistics])
 AC_PLUGIN([irq],         [$plugin_irq],        [IRQ statistics])
 AC_PLUGIN([libvirt],     [$plugin_libvirt],    [Virtual machine statistics])
@@ -2358,6 +2497,7 @@ AC_PLUGIN([ntpd],        [yes],                [NTPd statistics])
 AC_PLUGIN([nut],         [$with_libupsclient], [Network UPS tools statistics])
 AC_PLUGIN([perl],        [$plugin_perl],       [Embed a Perl interpreter])
 AC_PLUGIN([ping],        [$with_liboping],     [Network latency statistics])
+AC_PLUGIN([powerdns],    [yes],                [PowerDNS statistics])
 AC_PLUGIN([processes],   [$plugin_processes],  [Process statistics])
 AC_PLUGIN([rrdtool],     [$with_rrdtool],      [RRDTool output plugin])
 AC_PLUGIN([sensors],     [$with_lm_sensors],   [lm_sensors statistics])
@@ -2365,11 +2505,14 @@ AC_PLUGIN([serial],      [$plugin_serial],     [serial port traffic])
 AC_PLUGIN([snmp],        [$with_libnetsnmp],   [SNMP querying plugin])
 AC_PLUGIN([swap],        [$plugin_swap],       [Swap usage statistics])
 AC_PLUGIN([syslog],      [$have_syslog],       [Syslog logging plugin])
+AC_PLUGIN([tail],        [yes],                [Parsing of logfiles])
 AC_PLUGIN([tape],        [$plugin_tape],       [Tape drive statistics])
 AC_PLUGIN([tcpconns],    [$plugin_tcpconns],   [TCP connection statistics])
+AC_PLUGIN([teamspeak2],  [yes],                [TeamSpeak2 server statistics])
 AC_PLUGIN([unixsock],    [yes],                [Unixsock communication plugin])
 AC_PLUGIN([users],       [$plugin_users],      [User statistics])
 AC_PLUGIN([uuid],        [yes],                [UUID as hostname plugin])
+AC_PLUGIN([vmem],        [$plugin_vmem],       [Virtual memory statistics])
 AC_PLUGIN([vserver],     [$plugin_vserver],    [Linux VServer statistics])
 AC_PLUGIN([wireless],    [$plugin_wireless],   [Wireless statistics])
 AC_PLUGIN([xmms],        [$with_libxmms],      [XMMS statistics])
@@ -2411,7 +2554,7 @@ fi
 AC_SUBST(PERL_BINDINGS)
 AC_SUBST(PERL_BINDINGS_OPTIONS)
 
-AC_OUTPUT(Makefile src/Makefile src/collectd.conf src/liboconfig/Makefile src/liboping/Makefile bindings/Makefile)
+AC_OUTPUT(Makefile src/Makefile src/collectd.conf src/libiptc/Makefile src/liboconfig/Makefile src/liboping/Makefile bindings/Makefile)
 
 if test "x$with_rrdtool" = "xyes" \
        && test "x$librrd_threadsafe" != "xyes"
@@ -2425,6 +2568,11 @@ then
        with_liboping="yes (shipped version)"
 fi
 
+if test "x$with_libiptc" = "xyes" -a "x$with_own_libiptc" = "xyes"
+then
+       with_libiptc="yes (shipped version)"
+fi
+
 if test "x$with_libperl" = "xyes"
 then
        with_libperl="yes (version `$perl_interpreter -MConfig -e 'print $Config{version};'`)"
@@ -2451,6 +2599,7 @@ Configuration:
     libnetlink  . . . . $with_libnetlink
     libnetsnmp  . . . . $with_libnetsnmp
     liboconfig  . . . . $with_liboconfig
+    libopenipmi . . . . $with_libopenipmipthread
     liboping  . . . . . $with_liboping
     libpcap . . . . . . $with_libpcap
     libperl . . . . . . $with_libperl
@@ -2474,6 +2623,7 @@ Configuration:
     apache  . . . . . . $enable_apache
     apcups  . . . . . . $enable_apcups
     apple_sensors . . . $enable_apple_sensors
+    ascent  . . . . . . $enable_ascent
     battery . . . . . . $enable_battery
     cpu . . . . . . . . $enable_cpu
     cpufreq . . . . . . $enable_cpufreq
@@ -2487,6 +2637,7 @@ Configuration:
     hddtemp . . . . . . $enable_hddtemp
     interface . . . . . $enable_interface
     iptables  . . . . . $enable_iptables
+    ipmi  . . . . . . . $enable_ipmi
     ipvs  . . . . . . . $enable_ipvs
     irq . . . . . . . . $enable_irq
     libvirt . . . . . . $enable_libvirt
@@ -2505,6 +2656,7 @@ Configuration:
     nut . . . . . . . . $enable_nut
     perl  . . . . . . . $enable_perl
     ping  . . . . . . . $enable_ping
+    powerdns  . . . . . $enable_powerdns
     processes . . . . . $enable_processes
     rrdtool . . . . . . $enable_rrdtool
     sensors . . . . . . $enable_sensors
@@ -2512,11 +2664,14 @@ Configuration:
     snmp  . . . . . . . $enable_snmp
     swap  . . . . . . . $enable_swap
     syslog  . . . . . . $enable_syslog
+    tail  . . . . . . . $enable_tail
     tape  . . . . . . . $enable_tape
     tcpconns  . . . . . $enable_tcpconns
+    teamspeak2  . . . . $enable_teamspeak2
     unixsock  . . . . . $enable_unixsock
     users . . . . . . . $enable_users
     uuid  . . . . . . . $enable_uuid
+    vmem  . . . . . . . $enable_vmem
     vserver . . . . . . $enable_vserver
     wireless  . . . . . $enable_wireless
     xmms  . . . . . . . $enable_xmms
diff --git a/contrib/collection3/README b/contrib/collection3/README
new file mode 100644 (file)
index 0000000..01d01bb
--- /dev/null
@@ -0,0 +1,42 @@
+ collection3 - Web frontend for collectd
+=========================================
+http://collectd.org/
+
+About
+-----
+
+  collection3 is a graphing front-end for the RRD files created by and filled
+  with collectd. It is written in Perl and should be run as an CGI-script.
+  Graphs are generated on-the-fly, so no cron job or similar is necessary.
+
+Layout
+------
+
+  The files used by collection3 are organized in a typical UNIX fashion: The
+  configuration resides in etc/, executable scripts are in bin/, supplementary
+  Perl modules are in lib/ and static data for displaying the web page are in
+  share/.
+
+  All files in all subdirectories except bin/ should NOT be executable.
+  Ideally, the webserver should not serve them either. Consider using
+  `.htaccess' files or other means to configure the web server to deny access
+  to these directories.
+
+Dependencies
+------------
+
+  collection3 depends on the following Perl modules not included in the Perl
+  distribution itself:
+
+  * Config::General
+  * HTML::Entities
+  * RRDs
+
+Copyright and License
+---------------------
+
+  Copyright (C) 2008  Florian octo Forster <octo at verplant.org>
+
+  collection3 is provided under the terms of the GNU General Public License,
+  version 2 (GPLv2).
+
diff --git a/contrib/collection3/bin/graph.cgi b/contrib/collection3/bin/graph.cgi
new file mode 100755 (executable)
index 0000000..1524a66
--- /dev/null
@@ -0,0 +1,186 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use lib ('../lib');
+
+use FindBin ('$RealBin');
+use Carp (qw(confess cluck));
+use CGI (':cgi');
+use RRDs ();
+
+use Collectd::Graph::Config (qw(gc_read_config gc_get_scalar));
+use Collectd::Graph::TypeLoader (qw(tl_load_type));
+
+use Collectd::Graph::Common (qw(sanitize_type get_selected_files
+      epoch_to_rfc1123 flush_files));
+use Collectd::Graph::Type ();
+
+our $Debug = param ('debug');
+our $Begin = param ('begin');
+our $End = param ('end');
+our $GraphWidth = param ('width');
+
+if ($Debug)
+{
+  print <<HTTP;
+Content-Type: text/plain
+
+HTTP
+}
+
+gc_read_config ("$RealBin/../etc/collection.conf");
+
+if ($GraphWidth)
+{
+  $GraphWidth =~ s/\D//g;
+}
+
+if (!$GraphWidth)
+{
+  $GraphWidth = gc_get_scalar ('GraphWidth', 400);
+}
+
+{ # Sanitize begin and end times
+  $End ||= 0;
+  $Begin ||= 0;
+
+  if ($End =~ m/\D/)
+  {
+    $End = 0;
+  }
+
+  if (!$Begin || !($Begin =~ m/^-?([1-9][0-9]*)$/))
+  {
+    $Begin = -86400;
+  }
+
+  if ($Begin < 0)
+  {
+    if ($End)
+    {
+      $Begin = $End + $Begin;
+    }
+    else
+    {
+      $Begin = time () + $Begin;
+    }
+  }
+
+  if ($Begin < 0)
+  {
+    $Begin = time () - 86400;
+  }
+
+  if (($End > 0) && ($Begin > $End))
+  {
+    my $temp = $End;
+    $End = $Begin;
+    $Begin = $temp;
+  }
+}
+
+my $type = param ('type') or die;
+my $obj;
+
+$obj = tl_load_type ($type);
+if (!$obj)
+{
+  confess ("tl_load_type ($type) failed");
+}
+
+$type = ucfirst (lc ($type));
+$type =~ s/_([A-Za-z])/\U$1\E/g;
+$type = sanitize_type ($type);
+
+my $files = get_selected_files ();
+if ($Debug)
+{
+  require Data::Dumper;
+  print STDOUT Data::Dumper->Dump ([$files], ['files']);
+}
+for (@$files)
+{
+  $obj->addFiles ($_);
+}
+
+my $expires = time ();
+# IF (End is `now')
+#    OR (Begin is before `now' AND End is after `now')
+if (($End == 0) || (($Begin <= $expires) && ($End >= $expires)))
+{
+  # 400 == width in pixels
+  my $timespan;
+
+  if ($End == 0)
+  {
+    $timespan = $expires - $Begin;
+  }
+  else
+  {
+    $timespan = $End - $Begin;
+  }
+  $expires += int ($timespan / 400.0);
+}
+# IF (End is not `now')
+#    AND (End is before `now')
+# ==> Graph will never change again!
+elsif (($End > 0) && ($End < $expires))
+{
+  $expires += (366 * 86400);
+}
+elsif ($Begin > $expires)
+{
+  $expires = $Begin;
+}
+
+# Send FLUSH command to the daemon if necessary and possible.
+flush_files ($files,
+    begin => $Begin,
+    end => $End,
+    addr => gc_get_scalar ('UnixSockAddr', undef),
+    interval => gc_get_scalar ('Interval', 10));
+
+print STDOUT header (-Content_type => 'image/png',
+  -Last_Modified => epoch_to_rfc1123 ($obj->getLastModified ()),
+  -Expires => epoch_to_rfc1123 ($expires));
+
+if ($Debug)
+{
+  print "\$expires = $expires;\n";
+}
+
+my $args = $obj->getRRDArgs (0);
+
+if ($Debug)
+{
+  require Data::Dumper;
+  print STDOUT Data::Dumper->Dump ([$obj], ['obj']);
+  print STDOUT join (",\n", @$args) . "\n";
+  print STDOUT "Last-Modified: " . epoch_to_rfc1123 ($obj->getLastModified ()) . "\n";
+}
+else
+{
+  my @timesel = ();
+
+  if ($End) # $Begin is always true
+  {
+    @timesel = ('-s', $Begin, '-e', $End);
+  }
+  else
+  {
+    @timesel = ('-s', $Begin); # End is implicitely `now'.
+  }
+
+  $| = 1;
+  RRDs::graph ('-', '-a', 'PNG', '--width', $GraphWidth, @timesel, @$args);
+  if (my $err = RRDs::error ())
+  {
+    print STDERR "RRDs::graph failed: $err\n";
+    exit (1);
+  }
+}
+
+exit (0);
+
+# vim: set shiftwidth=2 softtabstop=2 tabstop=8 :
diff --git a/contrib/collection3/bin/index.cgi b/contrib/collection3/bin/index.cgi
new file mode 100755 (executable)
index 0000000..be20928
--- /dev/null
@@ -0,0 +1,388 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2008  Florian octo Forster <octo at verplant.org>
+#
+# 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; only version 2 of the License is applicable.
+#
+# 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.
+
+use strict;
+use warnings;
+use lib ('../lib');
+use utf8;
+
+use FindBin ('$RealBin');
+use CGI (':cgi');
+use CGI::Carp ('fatalsToBrowser');
+use HTML::Entities ('encode_entities');
+
+use Data::Dumper;
+
+use Collectd::Graph::Config (qw(gc_read_config gc_get_scalar));
+use Collectd::Graph::TypeLoader (qw(tl_load_type));
+use Collectd::Graph::Common (qw(get_files_from_directory get_all_hosts
+      get_timespan_selection get_selected_files get_host_selection
+      get_plugin_selection flush_files));
+use Collectd::Graph::Type ();
+
+our $Debug = param ('debug') ? 1 : 0;
+
+our $TimeSpans =
+{
+  Hour  =>        3600,
+  Day   =>       86400,
+  Week  =>   7 * 86400,
+  Month =>  31 * 86400,
+  Year  => 366 * 86400
+};
+
+my $action = param ('action') || 'list_hosts';
+our %Actions =
+(
+  list_hosts => \&action_list_hosts,
+  show_selection => \&action_show_selection
+);
+
+if (!exists ($Actions{$action}))
+{
+  print STDERR "No such action: $action\n";
+  exit 1;
+}
+
+gc_read_config ("$RealBin/../etc/collection.conf");
+
+$Actions{$action}->();
+exit (0);
+
+sub can_handle_xhtml
+{
+  my %types = ();
+
+  if (!defined $ENV{'HTTP_ACCEPT'})
+  {
+    return;
+  }
+
+  for (split (',', $ENV{'HTTP_ACCEPT'}))
+  {
+    my $type = lc ($_);
+    my $q = 1.0;
+
+    if ($type =~ m#^([^;]+);q=([0-9\.]+)$#)
+    {
+      $type = $1;
+      $q = 0.0 + $2;
+    }
+    $types{$type} = $q;
+  }
+
+  if (!defined ($types{'application/xhtml+xml'}))
+  {
+    return;
+  }
+  elsif (!defined ($types{'text/html'}))
+  {
+    return (1);
+  }
+  elsif ($types{'application/xhtml+xml'} < $types{'text/html'})
+  {
+    return;
+  }
+  else
+  {
+    return (1);
+  }
+} # can_handle_xhtml
+
+{my $html_started;
+sub start_html
+{
+  return if ($html_started);
+
+  my $end;
+  my $begin;
+  my $timespan;
+
+  $end = time ();
+  $timespan = get_timespan_selection ();
+  $begin = $end - $timespan;
+
+  if (can_handle_xhtml ())
+  {
+    print <<HTML;
+Content-Type: application/xhtml+xml; charset=UTF-8
+
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+    "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://www.w3.org/MarkUp/SCHEMA/xhtml11.xsd"
+    xml:lang="en">
+HTML
+  }
+  else
+  {
+    print <<HTML;
+Content-Type: text/html; charset=UTF-8
+
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
+    "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+HTML
+  }
+  print <<HTML;
+  <head>
+    <title>collection.cgi, Version 3</title>
+    <link rel="icon" href="../share/shortcut-icon.png" type="image/png" />
+    <link rel="stylesheet" href="../share/style.css" type="text/css" />
+    <script type="text/javascript" src="../share/navigate.js" />
+  </head>
+  <body onload="nav_init ($begin, $end);">
+HTML
+  $html_started = 1;
+}}
+
+sub end_html
+{
+  print <<HTML;
+  </body>
+</html>
+HTML
+}
+
+sub show_selector
+{
+  my $timespan_selection = get_timespan_selection ();
+  my $host_selection = get_host_selection ();
+  my $plugin_selection = get_plugin_selection ();
+
+  print <<HTML;
+    <form action="${\script_name ()}" method="get">
+      <fieldset>
+        <legend>Data selection</legend>
+        <select name="hostname" multiple="multiple" size="15">
+HTML
+  for (sort (keys %$host_selection))
+  {
+    my $host = encode_entities ($_);
+    my $selected = $host_selection->{$_}
+    ? ' selected="selected"'
+    : '';
+    print qq#          <option value="$host"$selected>$host</option>\n#;
+  }
+  print <<HTML;
+        </select>
+       <select name="plugin" multiple="multiple" size="15">
+HTML
+  for (sort (keys %$plugin_selection))
+  {
+    my $plugin = encode_entities ($_);
+    my $selected = $plugin_selection->{$_}
+    ? ' selected="selected"'
+    : '';
+    print qq#          <option value="$plugin"$selected>$plugin</option>\n#;
+  }
+  print <<HTML;
+       </select>
+       <select name="timespan">
+HTML
+  for (sort { $TimeSpans->{$a} <=> $TimeSpans->{$b} } (keys (%$TimeSpans)))
+  {
+    my $name = encode_entities ($_);
+    my $value = $TimeSpans->{$_};
+    my $selected = ($value == $timespan_selection)
+    ? ' selected="selected"'
+    : '';
+    print qq#          <option value="$value"$selected>$name</option>\n#;
+  }
+  print <<HTML;
+        </select>
+       <input type="hidden" name="action" value="show_selection" />
+       <input type="submit" name="ok_button" value="OK" />
+      </fieldset>
+    </form>
+HTML
+} # show_selector
+
+sub action_list_hosts
+{
+  start_html ();
+  show_selector ();
+
+  my @hosts = get_all_hosts ();
+  print "    <ul>\n";
+  for (sort @hosts)
+  {
+    my $url = encode_entities (script_name () . "?action=show_selection;hostname=$_");
+    my $name = encode_entities ($_);
+    print qq#      <li><a href="$url">$name</a></li>\n#;
+  }
+  print "    </ul>\n";
+
+  end_html ();
+} # action_list_hosts
+
+=head1 MODULE LOADING
+
+This script makes use of the various B<Collectd::Graph::Type::*> modules. If a
+file like C<foo.rrd> is encountered it tries to load the
+B<Collectd::Graph::Type::Foo> module and, if that fails, falls back to the
+B<Collectd::Graph::Type> base class.
+
+If you want to create a specialized graph for a certain type, you have to
+create a new module which inherits from the B<Collectd::Graph::Type> base
+class. A description of provided (and used) methods can be found in the inline
+documentation of the B<Collectd::Graph::Type> module.
+
+There are other, more specialized, "abstract" classes that possibly better fit
+your need. Unfortunately they are not yet documented.
+
+=over 4
+
+=item B<Collectd::Graph::Type::GenericStacked>
+
+Specialized class that groups files by their plugin instance and stacks them on
+top of each other. Example types that inherit from this class are
+B<Collectd::Graph::Type::Cpu> and B<Collectd::Graph::Type::Memory>.
+
+=item B<Collectd::Graph::Type::GenericIO>
+
+Specialized class for input/output graphs. This class can only handle files
+with exactly two data sources, input and output. Example types that inherit
+from this class are B<Collectd::Graph::Type::DiskOctets> and
+B<Collectd::Graph::Type::IfOctets>.
+
+=back
+
+=cut
+
+sub action_show_selection
+{
+  start_html ();
+  show_selector ();
+
+  my $all_files;
+  my $timespan;
+
+  my $types = {};
+
+  my $id_counter = 0;
+
+  $all_files = get_selected_files ();
+  $timespan = get_timespan_selection ();
+
+  if ($Debug)
+  {
+    print "<pre>", Data::Dumper->Dump ([$all_files], ['all_files']), "</pre>\n";
+  }
+
+  # Send FLUSH command to the daemon if necessary and possible.
+  flush_files ($all_files,
+      begin => time () - $timespan,
+      end => time (),
+      addr => gc_get_scalar ('UnixSockAddr', undef),
+      interval => gc_get_scalar ('Interval', 10));
+
+  for (@$all_files)
+  {
+    my $file = $_;
+    my $type = ucfirst (lc ($file->{'type'}));
+
+    $type =~ s/[^A-Za-z_]//g;
+    $type =~ s/_([A-Za-z])/\U$1\E/g;
+
+    if (!defined ($types->{$type}))
+    {
+      $types->{$type} = tl_load_type ($file->{'type'});
+      if (!$types->{$type})
+      {
+        cluck ("tl_load_type (" . $file->{'type'} . ") failed");
+        next;
+      }
+    }
+
+    $types->{$type}->addFiles ($file);
+  }
+#print STDOUT Data::Dumper->Dump ([$types], ['types']);
+
+  print qq#    <table>\n#;
+  for (sort (keys %$types))
+  {
+    my $type = $_;
+    my $graphs_num = $types->{$type}->getGraphsNum ();
+
+    for (my $i = 0; $i < $graphs_num; $i++)
+    {
+      my $args = $types->{$type}->getGraphArgs ($i);
+      my $url = encode_entities ("graph.cgi?$args;begin=-$timespan");
+      my $id = sprintf ("graph%04i", $id_counter++);
+
+      print "      <tr>\n";
+      print "        <td rowspan=\"$graphs_num\">$type</td>\n" if ($i == 0);
+      print <<EOF;
+        <td>
+          <div class="graph_canvas">
+            <div class="graph_float">
+              <img id="${id}" class="graph_image"
+                alt="A graph"
+                src="$url" />
+              <div class="controls zoom">
+                <div title="Earlier"
+                  onclick="nav_move_earlier ('${id}');">&#x2190;</div>
+                <div title="Zoom out"
+                  onclick="nav_zoom_out ('${id}');">-</div>
+                <div title="Zoom in"
+                  onclick="nav_zoom_in ('${id}');">+</div>
+                <div title="Later"
+                  onclick="nav_move_later ('${id}');">&#x2192;</div>
+              </div>
+              <div class="controls preset">
+                <div title="Show current hour"
+                  onclick="nav_time_reset ('${id}', 3600);">H</div>
+                <div title="Show current day"
+                  onclick="nav_time_reset ('${id}', 86400);">D</div>
+                <div title="Show current week"
+                  onclick="nav_time_reset ('${id}', 7 * 86400);">W</div>
+                <div title="Show current month"
+                  onclick="nav_time_reset ('${id}', 31 * 86400);">M</div>
+                <div title="Show current year"
+                  onclick="nav_time_reset ('${id}', 366 * 86400);">Y</div>
+                <div title="Set all images to this timespan"
+                  onclick="nav_set_reference ('${id}');">!</div>
+              </div>
+            </div>
+          </div>
+       </td>
+EOF
+      # print qq#        <td><img src="$url" /></td>\n#;
+      print "      </tr>\n";
+    }
+  }
+
+  print "    </table>\n";
+  end_html ();
+}
+
+=head1 SEE ALSO
+
+L<Collectd::Graph::Type>
+
+=head1 AUTHOR AND LICENSE
+
+Copyright (c) 2008 by Florian Forster
+E<lt>octoE<nbsp>atE<nbsp>verplant.orgE<gt>. Licensed under the terms of the GNU
+General Public License, VersionE<nbsp>2 (GPLv2).
+
+=cut
+
+# vim: set shiftwidth=2 softtabstop=2 tabstop=8 :
diff --git a/contrib/collection3/bin/json.cgi b/contrib/collection3/bin/json.cgi
new file mode 100755 (executable)
index 0000000..c2af61c
--- /dev/null
@@ -0,0 +1,121 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2008  Florian octo Forster <octo at verplant.org>
+#
+# 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; only version 2 of the License is applicable.
+#
+# 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.
+
+use strict;
+use warnings;
+use lib ('../lib');
+use utf8;
+
+use FindBin ('$RealBin');
+use CGI (':cgi');
+use CGI::Carp ('fatalsToBrowser');
+use URI::Escape ('uri_escape');
+
+use Data::Dumper;
+
+use Collectd::Graph::Config (qw(gc_read_config));
+use Collectd::Graph::TypeLoader (qw(tl_load_type));
+use Collectd::Graph::Common (qw(get_all_hosts get_files_for_host type_to_module_name));
+use Collectd::Graph::Type ();
+
+our $Debug = param ('debug') ? 1 : 0;
+
+gc_read_config ("$RealBin/../etc/collection.conf");
+
+if ($Debug)
+{
+  print "Content-Type: text/plain; charset=utf-8\n\n";
+}
+else
+{
+  print "Content-Type: application/json; charset=utf-8\n\n";
+}
+
+print "{\n";
+
+my @hosts = get_all_hosts ();
+for (my $i = 0; $i < @hosts; $i++)
+{
+  my $host = $hosts[$i];
+  my $files = get_files_for_host ($host);
+  my %graphs = ();
+  my @graphs = ();
+
+  # Group files by graphs
+  for (@$files)
+  {
+    my $file = $_;
+    my $type = $file->{'type'};
+
+    # Create a new graph object if this is the first of this type.
+    if (!defined ($graphs{$type}))
+    {
+      $graphs{$type} = tl_load_type ($file->{'type'});
+      if (!$graphs{$type})
+      {
+        cluck ("tl_load_type (" . $file->{'type'} . ") failed");
+        next;
+      }
+    }
+
+    $graphs{$type}->addFiles ($file);
+  } # for (@$files)
+
+  print qq(  "$host":\n  {\n);
+
+  @graphs = keys %graphs;
+  for (my $j = 0; $j < @graphs; $j++)
+  {
+    my $type = $graphs[$j];
+    my $graphs_num = $graphs{$type}->getGraphsNum ();
+    my @args = ();
+
+    for (my $k = 0; $k < $graphs_num; $k++)
+    {
+      my $args = $graphs{$type}->getGraphArgs ($k);
+      my $url = 'http://' . $ENV{'SERVER_NAME'} . "/cgi-bin/graph.cgi?" . $args;
+      push (@args, $url);
+    }
+
+    print qq(    "$type": [ )
+    . join (', ', map { qq("$_") } (@args));
+
+    if ($j == (@graphs - 1))
+    {
+      print qq( ]\n);
+    }
+    else
+    {
+      print qq( ],\n);
+    }
+  } # for (keys %graphs)
+
+  if ($i == (@hosts - 1))
+  {
+    print qq(  }\n);
+  }
+  else
+  {
+    print qq(  },\n\n);
+  }
+} # for (my $i = 0; $i < @hosts; $i++)
+
+print "}\n";
+
+exit (0);
+
+# vim: set shiftwidth=2 softtabstop=2 tabstop=8 :
diff --git a/contrib/collection3/etc/.htaccess b/contrib/collection3/etc/.htaccess
new file mode 100644 (file)
index 0000000..3a42882
--- /dev/null
@@ -0,0 +1 @@
+Deny from all
diff --git a/contrib/collection3/etc/collection.conf b/contrib/collection3/etc/collection.conf
new file mode 100644 (file)
index 0000000..a836cf2
--- /dev/null
@@ -0,0 +1,350 @@
+GraphWidth 400
+UnixSockAddr "/var/run/collectd-unixsock"
+<Type apache_scoreboard>
+  Module GenericStacked
+  DataSources count
+  RRDTitle "Apache scoreboard on {hostname}"
+  RRDVerticalLabel "Slots"
+  RRDFormat "%6.2lf"
+  DSName closing Closing
+  DSName dnslookup DNS lookup
+  DSName finishing Finishing
+  DSName idle_cleanup Idle cleanup
+  DSName keepalive Keep alive
+  DSName logging Logging
+  DSName open Open (empty)
+  DSName reading Reading
+  DSName sending Sending
+  DSName starting Starting
+  DSName waiting Waiting
+  Order open closing dnslookup finishing idle_cleanup keepalive logging open reading sending starting waiting
+  Color closing      000080
+  Color dnslookup    ff0000
+  Color finishing    008080
+  Color idle_cleanup ffff00
+  Color keepalive    0080ff
+  Color logging      a000a0
+  Color open         e0e0e0
+  Color reading      0000ff
+  Color sending      00e000
+  Color starting     ff00ff
+  Color waiting      ffb000
+</Type>
+<Type cpu>
+  Module GenericStacked
+  DataSources value
+  RRDTitle "CPU {plugin_instance} usage"
+  RRDVerticalLabel "Jiffies"
+  RRDFormat "%5.2lf"
+  DSName idle Idle
+  DSName nice Nice
+  DSName user User
+  DSName wait Wait-IO
+  DSName system System
+  DSName softirq SoftIRQ
+  DSName interrupt IRQ
+  DSName steal Steal
+  Order idle nice user wait system softirq interrupt steal
+  Color idle      e8e8e8
+  Color nice      00e000
+  Color user      0000ff
+  Color wait      ffb000
+  Color system    ff0000
+  Color softirq   ff00ff
+  Color interrupt a000a0
+  Color steal     000000
+</Type>
+<Type current>
+  DataSources value
+  DSName value Current
+  RRDTitle "Current ({type_instance})"
+  RRDVerticalLabel "Ampere"
+  RRDFormat "%4.1lfA"
+  Color value ffb000
+</Type>
+<Type df>
+  Module Df
+  DataSources free used
+</Type>
+<Type disk_octets>
+  Module GenericIO
+  DataSources read write
+  DSName "read Read   "
+  DSName write Written
+  RRDTitle "Disk Traffic ({plugin_instance})"
+  RRDVerticalLabel "Bytes per second"
+# RRDOptions ...
+  RRDFormat "%5.1lf%s"
+</Type>
+<Type disk_ops>
+  Module GenericIO
+  DataSources read write
+  DSName "read Read   "
+  DSName write Written
+  RRDTitle "Disk Operations ({plugin_instance})"
+  RRDVerticalLabel "Operations per second"
+# RRDOptions ...
+  RRDFormat "%5.1lf"
+</Type>
+<Type disk_merged>
+  Module GenericIO
+  DataSources read write
+  DSName "read Read   "
+  DSName write Written
+  RRDTitle "Disk Merged Operations ({plugin_instance})"
+  RRDVerticalLabel "Merged operations/s"
+# RRDOptions ...
+  RRDFormat "%5.1lf"
+</Type>
+<Type disk_time>
+  Module GenericIO
+  DataSources read write
+  DSName "read Read   "
+  DSName write Written
+  RRDTitle "Disk time per operation ({plugin_instance})"
+  RRDVerticalLabel "Avg. Time/Op"
+# RRDOptions ...
+  RRDFormat "%5.1lf%ss"
+  Scale 0.001
+</Type>
+<Type entropy>
+  DataSources entropy
+  DSName entropy Entropy bits
+  RRDTitle "Available entropy on {hostname}"
+  RRDVerticalLabel "Bits"
+  RRDFormat "%4.0lf"
+</Type>
+<Type fanspeed>
+  DataSources value
+  DSName value RPM
+  RRDTitle "Fanspeed ({instance})"
+  RRDVerticalLabel "RPM"
+  RRDFormat "%6.1lf"
+  Color value 00b000
+</Type>
+<Type frequency>
+  DataSources frequency
+  DSName frequency Frequency
+  RRDTitle "Frequency ({type_instance})"
+  RRDVerticalLabel "Hertz"
+  RRDFormat "%4.1lfHz"
+  Color frequency a000a0
+</Type>
+<Type humidity>
+  DataSources value
+  DSName value Humitidy
+  RRDTitle "Humitidy ({instance})"
+  RRDVerticalLabel "Percent"
+  RRDFormat "%4.1lf%%"
+  Color value 00e000
+</Type>
+<Type if_errors>
+  Module GenericIO
+  DataSources rx tx
+  DSName rx RX
+  DSName tx TX
+  RRDTitle "Interface Errors ({type_instance})"
+  RRDVerticalLabel "Errors per second"
+# RRDOptions ...
+  RRDFormat "%.3lf"
+</Type>
+<Type if_rx_errors>
+  Module GenericStacked
+  DataSources value
+  RRDTitle "Interface receive errors ({plugin_instance})"
+  RRDVerticalLabel "Erorrs/s"
+  RRDFormat "%.1lf"
+  Color length  f00000
+  Color over    00e0ff
+  Color crc     00e000
+  Color frame   ffb000
+  Color fifo    f000c0
+  Color missed  0000f0
+</Type>
+<Type if_tx_errors>
+  Module GenericStacked
+  DataSources value
+  RRDTitle "Interface transmit errors ({plugin_instance})"
+  RRDVerticalLabel "Erorrs/s"
+  RRDFormat "%.1lf"
+  Color aborted   f00000
+  Color carrier   00e0ff
+  Color fifo      00e000
+  Color heartbeat ffb000
+  Color window    f000c0
+</Type>
+<Type if_octets>
+  Module GenericIO
+  DataSources rx tx
+  DSName rx RX
+  DSName tx TX
+  RRDTitle "Interface Traffic ({type_instance})"
+  RRDVerticalLabel "Bits per second"
+# RRDOptions ...
+  RRDFormat "%5.1lf%s"
+  Scale 8
+</Type>
+<Type if_packets>
+  Module GenericIO
+  DataSources rx tx
+  DSName rx RX
+  DSName tx TX
+  RRDTitle "Interface Packets ({type_instance})"
+  RRDVerticalLabel "Packets per second"
+# RRDOptions ...
+  RRDFormat "%5.1lf%s"
+</Type>
+<Type ipt_bytes>
+  DataSources value
+  DSName value Bytes/s
+  RRDTitle "Traffic ({type_instance})"
+  RRDVerticalLabel "Bytes per second"
+# RRDOptions ...
+  RRDFormat "%5.1lf%s"
+</Type>
+<Type ipt_packets>
+  DataSources value
+  DSName value Packets/s
+  RRDTitle "Packets ({type_instance})"
+  RRDVerticalLabel "Packets per second"
+# RRDOptions ...
+  RRDFormat "%5.1lf"
+</Type>
+<Type irq>
+  Module GenericStacked
+  DataSources value
+  RRDTitle "Interrupts on {hostname}"
+  RRDVerticalLabel "IRQs/s"
+  RRDFormat "%5.1lf"
+</Type>
+<Type load>
+  Module Load
+</Type>
+<Type memory>
+  Module GenericStacked
+  DataSources value
+  RRDTitle "Physical memory utilization on {hostname}"
+  RRDVerticalLabel "Bytes"
+  RRDFormat "%5.1lf%s"
+  RRDOptions -b 1024 -l 0
+  DSName     "free Free    "
+  DSName   "cached Cached  "
+  DSName "buffered Buffered"
+  DSName     "used Used    "
+  #Order used buffered cached free
+  Order free cached buffered used
+  Color free      00e000
+  Color cached    0000ff
+  Color buffered  ffb000
+  Color used      ff0000
+</Type>
+<Type percent>
+  DataSources percent
+  DSName percent Percent
+  RRDTitle "Percent ({type_instance})"
+  RRDVerticalLabel "Percent"
+  RRDFormat "%4.1lf%%"
+  Color percent 0000ff
+</Type>
+<Type ping>
+  DataSources ping
+  DSName "ping Latency"
+  RRDTitle "Network latency ({type_instance})"
+  RRDVerticalLabel "Milliseconds"
+  RRDFormat "%5.2lfms"
+</Type>
+<Type power>
+  DataSources value
+  DSName value Watts
+  RRDTitle "Power ({type_instance})"
+  RRDVerticalLabel "Watts"
+  RRDFormat "%6.2lf%sW"
+  Color value 008080
+</Type>
+<Type ps_rss>
+  DataSources value
+  DSName value RSS
+  RRDTitle "Resident Segment Size ({instance})"
+  RRDVerticalLabel "Bytes"
+  RRDFormat "%6.2lf%s"
+</Type>
+<Type ps_state>
+  Module GenericStacked
+  DataSources value
+  RRDTitle "Processes on {hostname}"
+  RRDVerticalLabel "Processes"
+  RRDFormat "%5.1lf%s"
+  DSName running  Running
+  DSName sleeping Sleeping
+  DSName paging   Paging
+  DSName zombies  Zombies
+  DSName blocked  Blocked
+  DSName stopped  Stopped
+  Order paging blocked zombies stopped running sleeping
+  Color running  00e000
+  Color sleeping 0000ff
+  Color paging   ffb000
+  Color zombies  ff0000
+  Color blocked  ff00ff
+  Color stopped  a000a0
+</Type>
+<Type swap>
+  Module GenericStacked
+  DataSources value
+  RRDTitle "Swap utilization on {hostname}"
+  RRDVerticalLabel "Bytes"
+  RRDFormat "%5.1lf%s"
+  RRDOptions -b 1024 -l 0
+  DSName     "free Free    "
+  DSName   "cached Cached  "
+  DSName     "used Used    "
+  #Order used cached free
+  Order free cached used
+  Color free      00e000
+  Color cached    0000ff
+  Color used      ff0000
+</Type>
+<Type tcp_connections>
+  Module GenericStacked
+  DataSources value
+  RRDTitle "TCP connections ({plugin_instance})"
+  RRDVerticalLabel "Connections"
+  RRDFormat "%5.1lf"
+  Order LISTEN CLOSING LAST_ACK CLOSE_WAIT CLOSE TIME_WAIT FIN_WAIT2 FIN_WAIT1 SYN_RECV SYN_SENT ESTABLISHED CLOSED
+  Color ESTABLISHED 00e000
+  Color SYN_SENT   00e0ff
+  Color SYN_RECV   00e0a0
+  Color FIN_WAIT1  f000f0
+  Color FIN_WAIT2  f000a0
+  Color TIME_WAIT  ffb000
+  Color CLOSE      0000f0
+  Color CLOSE_WAIT 0000a0
+  Color LAST_ACK   000080
+  Color LISTEN     ff0000
+  Color CLOSING    000000
+  Color CLOSED     0000f0
+</Type>
+<Type temperature>
+  DataSources value
+  DSName value Temp
+  RRDTitle "Temperature ({instance})"
+  RRDVerticalLabel "°Celsius"
+  RRDFormat "%4.1lf°C"
+</Type>
+<Type users>
+  DataSources users
+  DSName users Users
+  RRDTitle "Users ({type_instance}) on {hostname}"
+  RRDVerticalLabel "Users"
+  RRDFormat "%.1lf"
+  Color users 0000f0
+</Type>
+<Type voltage>
+  DataSources value
+  DSName value Volts
+  RRDTitle "Voltage ({type_instance})"
+  RRDVerticalLabel "Volts"
+  RRDFormat "%4.1lfV"
+  Color value f00000
+</Type>
+# vim: set sw=2 sts=2 et syntax=apache fileencoding=latin-1 :
diff --git a/contrib/collection3/lib/.htaccess b/contrib/collection3/lib/.htaccess
new file mode 100644 (file)
index 0000000..3a42882
--- /dev/null
@@ -0,0 +1 @@
+Deny from all
diff --git a/contrib/collection3/lib/Collectd/Config.pm b/contrib/collection3/lib/Collectd/Config.pm
new file mode 100644 (file)
index 0000000..d20be35
--- /dev/null
@@ -0,0 +1,144 @@
+package Collectd::Graph::Config;
+
+=head1 NAME
+
+Collectd::Graph::Config - Parse the collection3 config file.
+
+=cut
+
+# Copyright (C) 2008  Florian octo Forster <octo at verplant.org>
+#
+# 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; only version 2 of the License is applicable.
+#
+# 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.
+
+use strict;
+use warnings;
+
+use Carp (qw(cluck confess));
+use Exporter ();
+use Config::General ('ParseConfig');
+use Collectd::Graph::Type ();
+
+@Collectd::Graph::Config::ISA = ('Exporter');
+@Collectd::Graph::Config::EXPORT_OK = (qw(gc_read_config gc_get_config
+  gc_get_scalar));
+
+our $Configuration = undef;
+
+return (1);
+
+=head1 EXPORTED FUNCTIONS
+
+=over 4
+
+=item B<gc_read_config> (I<$file>)
+
+Reads the configuration from the file located at I<$file>. Returns B<true> when
+successfull and B<false> otherwise.
+
+=cut
+
+sub gc_read_config
+{
+  my $file = shift;
+  my %conf;
+
+  if ($Configuration)
+  {
+    return (1);
+  }
+
+  $file ||= "etc/collection.conf";
+
+  %conf = ParseConfig (-ConfigFile => $file,
+    -LowerCaseNames => 1,
+    -UseApacheInclude => 1,
+    -IncludeDirectories => 1,
+    ($Config::General::VERSION >= 2.38) ? (-IncludeAgain => 0) : (),
+    -MergeDuplicateBlocks => 1,
+    -CComments => 0);
+  if (!%conf)
+  {
+    return;
+  }
+
+  $Configuration = \%conf;
+  return (1);
+} # gc_read_config
+
+=item B<gc_get_config> ()
+
+Returns the hash as provided by L<Config::General>. The hash is returned as a
+hash reference. Don't change it!
+
+=cut
+
+sub gc_get_config
+{
+  return ($Configuration);
+} # gc_get_config
+
+=item B<gc_get_config> (I<$key>, [I<$default>])
+
+Returns the scalar value I<$key> from the config file. If the key does not
+exist, I<$default> will be returned. If no default is given, B<undef> will be
+used in this case.
+
+=cut
+
+sub gc_get_scalar
+{
+  my $key = shift;
+  my $default = (@_ != 0) ? shift : undef;
+  my $value;
+
+  if (!$Configuration)
+  {
+    return ($default);
+  }
+
+  $value = $Configuration->{lc ($key)};
+  if (!defined ($value))
+  {
+    return ($default);
+  }
+
+  if (ref ($value) ne '')
+  {
+    cluck ("Value for `$key' should be scalar, but actually is "
+      . ref ($value));
+    return ($default);
+  }
+
+  return ($value);
+} # gc_get_config
+
+=back
+
+=head1 DEPENDS ON
+
+L<Config::General>
+
+=head1 SEE ALSO
+
+L<Collectd::Graph::Type>
+
+=head1 AUTHOR AND LICENSE
+
+Copyright (c) 2008 by Florian Forster
+E<lt>octoE<nbsp>atE<nbsp>verplant.orgE<gt>. Licensed under the terms of the GNU
+General Public License, VersionE<nbsp>2 (GPLv2).
+
+=cut
+
+# vim: set shiftwidth=2 softtabstop=2 tabstop=8 et fdm=marker :
diff --git a/contrib/collection3/lib/Collectd/Graph/Common.pm b/contrib/collection3/lib/Collectd/Graph/Common.pm
new file mode 100644 (file)
index 0000000..6f26fd8
--- /dev/null
@@ -0,0 +1,698 @@
+package Collectd::Graph::Common;
+
+use strict;
+use warnings;
+
+use vars (qw($ColorCanvas $ColorFullBlue $ColorHalfBlue));
+
+use Carp (qw(confess cluck));
+use CGI (':cgi');
+use Exporter;
+
+$ColorCanvas   = 'FFFFFF';
+$ColorFullBlue = '0000FF';
+$ColorHalfBlue = 'B7B7F7';
+
+@Collectd::Graph::Common::ISA = ('Exporter');
+@Collectd::Graph::Common::EXPORT_OK = (qw(
+  $ColorCanvas
+  $ColorFullBlue
+  $ColorHalfBlue
+
+  sanitize_hostname
+  sanitize_plugin sanitize_plugin_instance
+  sanitize_type sanitize_type_instance
+  group_files_by_plugin_instance
+  get_files_from_directory
+  filename_to_ident
+  ident_to_filename
+  ident_to_string
+  get_all_hosts
+  get_files_for_host
+  get_files_by_ident
+  get_selected_files
+  get_timespan_selection
+  get_host_selection
+  get_plugin_selection
+  get_faded_color
+  sort_idents_by_type_instance
+  type_to_module_name
+  epoch_to_rfc1123
+  flush_files
+));
+
+our $DataDir = '/var/lib/collectd/rrd';
+
+return (1);
+
+sub _sanitize_generic_allow_minus
+{
+  my $str = "" . shift;
+
+  # remove all slashes
+  $str =~ s#/##g;
+
+  # remove all dots and dashes at the beginning and at the end.
+  $str =~ s#^[\.-]+##;
+  $str =~ s#[\.-]+$##;
+
+  return ($str);
+}
+
+sub _sanitize_generic_no_minus
+{
+  # Do everything the allow-minus variant does..
+  my $str = _sanitize_generic_allow_minus (@_);
+
+  # .. and remove the dashes, too
+  $str =~ s#/##g;
+
+  return ($str);
+} # _sanitize_generic_no_minus
+
+sub sanitize_hostname
+{
+  return (_sanitize_generic_allow_minus (@_));
+}
+
+sub sanitize_plugin
+{
+  return (_sanitize_generic_no_minus (@_));
+}
+
+sub sanitize_plugin_instance
+{
+  return (_sanitize_generic_allow_minus (@_));
+}
+
+sub sanitize_type
+{
+  return (_sanitize_generic_no_minus (@_));
+}
+
+sub sanitize_type_instance
+{
+  return (_sanitize_generic_allow_minus (@_));
+}
+
+sub group_files_by_plugin_instance
+{
+  my @files = @_;
+  my $data = {};
+
+  for (my $i = 0; $i < @files; $i++)
+  {
+    my $file = $files[$i];
+    my $key = $file->{'plugin_instance'} || '';
+
+    $data->{$key} ||= [];
+    push (@{$data->{$key}}, $file);
+  }
+
+  return ($data);
+}
+
+sub filename_to_ident
+{
+  my $file = shift;
+  my $ret;
+
+  if ($file =~ m#([^/]+)/([^/\-]+)(?:-([^/]+))?/([^/\-]+)(?:-([^/]+))?\.rrd$#)
+  {
+    $ret = {hostname => $1, plugin => $2, type => $4};
+    if (defined ($3))
+    {
+      $ret->{'plugin_instance'} = $3;
+    }
+    if (defined ($5))
+    {
+      $ret->{'type_instance'} = $5;
+    }
+    if ($`)
+    {
+      $ret->{'_prefix'} = $`;
+    }
+  }
+  else
+  {
+    return;
+  }
+
+  return ($ret);
+} # filename_to_ident
+
+sub ident_to_filename
+{
+  my $ident = shift;
+
+  my $ret = '';
+
+  if (defined ($ident->{'_prefix'}))
+  {
+    $ret .= $ident->{'_prefix'};
+  }
+  else
+  {
+    $ret .= "$DataDir/";
+  }
+
+  if (!$ident->{'hostname'})
+  {
+    cluck ("hostname is undefined")
+  }
+  if (!$ident->{'plugin'})
+  {
+    cluck ("plugin is undefined")
+  }
+  if (!$ident->{'type'})
+  {
+    cluck ("type is undefined")
+  }
+
+  $ret .= $ident->{'hostname'} . '/' . $ident->{'plugin'};
+  if (defined ($ident->{'plugin_instance'}))
+  {
+    $ret .= '-' . $ident->{'plugin_instance'};
+  }
+
+  $ret .= '/' . $ident->{'type'};
+  if (defined ($ident->{'type_instance'}))
+  {
+    $ret .= '-' . $ident->{'type_instance'};
+  }
+  $ret .= '.rrd';
+
+  return ($ret);
+} # ident_to_filename
+
+sub ident_to_string
+{
+  my $ident = shift;
+
+  my $ret = '';
+
+  $ret .= $ident->{'hostname'} . '/' . $ident->{'plugin'};
+  if (defined ($ident->{'plugin_instance'}))
+  {
+    $ret .= '-' . $ident->{'plugin_instance'};
+  }
+
+  $ret .= '/' . $ident->{'type'};
+  if (defined ($ident->{'type_instance'}))
+  {
+    $ret .= '-' . $ident->{'type_instance'};
+  }
+
+  return ($ret);
+} # ident_to_string
+
+sub get_files_from_directory
+{
+  my $dir = shift;
+  my $recursive = @_ ? shift : 0;
+  my $dh;
+  my @directories = ();
+  my $ret = [];
+
+  opendir ($dh, $dir) or die ("opendir ($dir): $!");
+  while (my $entry = readdir ($dh))
+  {
+    next if ($entry =~ m/^\./);
+
+    $entry = "$dir/$entry";
+
+    if (-d $entry)
+    {
+      push (@directories, $entry);
+    }
+    elsif (-f $entry)
+    {
+      my $ident = filename_to_ident ($entry);
+      if ($ident)
+      {
+       push (@$ret, $ident);
+      }
+    }
+  }
+  closedir ($dh);
+
+  if ($recursive > 0)
+  {
+    for (@directories)
+    {
+      my $temp = get_files_from_directory ($_, $recursive - 1);
+      if ($temp && @$temp)
+      {
+       push (@$ret, @$temp);
+      }
+    }
+  }
+
+  return ($ret);
+} # get_files_from_directory
+
+sub get_all_hosts
+{
+  my $dh;
+  my @ret = ();
+
+  opendir ($dh, "$DataDir") or confess ("opendir ($DataDir): $!");
+  while (my $entry = readdir ($dh))
+  {
+    next if ($entry =~ m/^\./);
+    next if (!-d "$DataDir/$entry");
+    push (@ret, sanitize_hostname ($entry));
+  }
+  closedir ($dh);
+
+  if (wantarray ())
+  {
+    return (@ret);
+  }
+  elsif (@ret)
+  {
+    return (\@ret);
+  }
+  else
+  {
+    return;
+  }
+} # get_all_hosts
+
+sub get_all_plugins
+{
+  my @hosts = @_;
+  my $ret = {};
+  my $dh;
+
+  if (!@hosts)
+  {
+    @hosts = get_all_hosts ();
+  }
+
+  for (@hosts)
+  {
+    my $host = $_;
+    opendir ($dh, "$DataDir/$host") or next;
+    while (my $entry = readdir ($dh))
+    {
+      my $plugin;
+      my $plugin_instance = '';
+
+      next if ($entry =~ m/^\./);
+      next if (!-d "$DataDir/$host/$entry");
+
+      if ($entry =~ m#^([^-]+)-(.+)$#)
+      {
+       $plugin = $1;
+       $plugin_instance = $2;
+      }
+      elsif ($entry =~ m#^([^-]+)$#)
+      {
+       $plugin = $1;
+       $plugin_instance = '';
+      }
+      else
+      {
+       next;
+      }
+
+      $ret->{$plugin} ||= {};
+      $ret->{$plugin}{$plugin_instance} = 1;
+    } # while (readdir)
+    closedir ($dh);
+  } # for (@hosts)
+
+  if (wantarray ())
+  {
+    return (sort (keys %$ret));
+  }
+  else
+  {
+    return ($ret);
+  }
+} # get_all_plugins
+
+sub get_files_for_host
+{
+  my $host = sanitize_hostname (shift);
+  return (get_files_from_directory ("$DataDir/$host", 2));
+} # get_files_for_host
+
+sub _filter_ident
+{
+  my $filter = shift;
+  my $ident = shift;
+
+  for (qw(hostname plugin plugin_instance type type_instance))
+  {
+    my $part = $_;
+    my $tmp;
+
+    if (!defined ($filter->{$part}))
+    {
+      next;
+    }
+    if (!defined ($ident->{$part}))
+    {
+      return (1);
+    }
+
+    if (ref $filter->{$part})
+    {
+      if (!grep { $ident->{$part} eq $_ } (@{$filter->{$part}}))
+      {
+       return (1);
+      }
+    }
+    else
+    {
+      if ($ident->{$part} ne $filter->{$part})
+      {
+       return (1);
+      }
+    }
+  }
+
+  return (0);
+} # _filter_ident
+
+sub get_files_by_ident
+{
+  my $ident = shift;
+  my $all_files;
+  my @ret = ();
+
+  #if ($ident->{'hostname'})
+  #{
+  #$all_files = get_files_for_host ($ident->{'hostname'});
+  #}
+  #else
+  #{
+    $all_files = get_files_from_directory ($DataDir, 3);
+    #}
+
+  @ret = grep { _filter_ident ($ident, $_) == 0 } (@$all_files);
+
+  return (\@ret);
+} # get_files_by_ident
+
+sub get_selected_files
+{
+  my $ident = {};
+  
+  for (qw(hostname plugin plugin_instance type type_instance))
+  {
+    my $part = $_;
+    my @temp = param ($part);
+    if (!@temp)
+    {
+      next;
+    }
+    elsif (($part eq 'plugin') || ($part eq 'type'))
+    {
+      $ident->{$part} = [map { _sanitize_generic_no_minus ($_) } (@temp)];
+    }
+    else
+    {
+      $ident->{$part} = [map { _sanitize_generic_allow_minus ($_) } (@temp)];
+    }
+  }
+
+  return (get_files_by_ident ($ident));
+} # get_selected_files
+
+sub get_timespan_selection
+{
+  my $ret = 86400;
+  if (param ('timespan'))
+  {
+    my $temp = int (param ('timespan'));
+    if ($temp && ($temp > 0))
+    {
+      $ret = $temp;
+    }
+  }
+
+  return ($ret);
+} # get_timespan_selection
+
+sub get_host_selection
+{
+  my %ret = ();
+
+  for (get_all_hosts ())
+  {
+    $ret{$_} = 0;
+  }
+
+  for (param ('hostname'))
+  {
+    my $host = _sanitize_generic_allow_minus ($_);
+    if (defined ($ret{$host}))
+    {
+      $ret{$host} = 1;
+    }
+  }
+
+  if (wantarray ())
+  {
+    return (grep { $ret{$_} > 0 } (sort (keys %ret)));
+  }
+  else
+  {
+    return (\%ret);
+  }
+} # get_host_selection
+
+sub get_plugin_selection
+{
+  my %ret = ();
+  my @hosts = get_host_selection ();
+
+  for (get_all_plugins (@hosts))
+  {
+    $ret{$_} = 0;
+  }
+
+  for (param ('plugin'))
+  {
+    if (defined ($ret{$_}))
+    {
+      $ret{$_} = 1;
+    }
+  }
+
+  if (wantarray ())
+  {
+    return (grep { $ret{$_} > 0 } (sort (keys %ret)));
+  }
+  else
+  {
+    return (\%ret);
+  }
+} # get_plugin_selection
+
+sub _string_to_color
+{
+  my $color = shift;
+  if ($color =~ m/([0-9A-Fa-f][0-9A-Fa-f])([0-9A-Fa-f][0-9A-Fa-f])([0-9A-Fa-f][0-9A-Fa-f])/)
+  {
+    return ([hex ($1) / 255.0, hex ($2) / 255.0, hex ($3) / 255.0]);
+  }
+  return;
+} # _string_to_color
+
+sub _color_to_string
+{
+  confess ("Wrong number of arguments") if (@_ != 1);
+  return (sprintf ('%02hx%02hx%02hx', map { int (255.0 * $_) } @{$_[0]}));
+} # _color_to_string
+
+sub get_faded_color
+{
+  my $fg = shift;
+  my $bg;
+  my %opts = @_;
+  my $ret = [undef, undef, undef];
+
+  $opts{'background'} ||= [1.0, 1.0, 1.0];
+  $opts{'alpha'} ||= 0.25;
+
+  if (!ref ($fg))
+  {
+    $fg = _string_to_color ($fg)
+      or confess ("Cannot parse foreground color $fg");
+  }
+
+  if (!ref ($opts{'background'}))
+  {
+    $opts{'background'} = _string_to_color ($opts{'background'})
+      or confess ("Cannot parse background color " . $opts{'background'});
+  }
+  $bg = $opts{'background'};
+
+  for (my $i = 0; $i < 3; $i++)
+  {
+    $ret->[$i] = ($opts{'alpha'} * $fg->[$i])
+       + ((1.0 - $opts{'alpha'}) * $bg->[$i]);
+  }
+
+  return (_color_to_string ($ret));
+} # get_faded_color
+
+sub sort_idents_by_type_instance
+{
+  my $idents = shift;
+  my $array_sort = shift;
+
+  my %elements = map { $_->{'type_instance'} => $_ } (@$idents);
+  splice (@$idents, 0);
+
+  for (@$array_sort)
+  {
+    next if (!exists ($elements{$_}));
+    push (@$idents, $elements{$_});
+    delete ($elements{$_});
+  }
+  push (@$idents, map { $elements{$_} } (sort (keys %elements)));
+} # sort_idents_by_type_instance
+
+sub type_to_module_name
+{
+  my $type = shift;
+  my $ret;
+  
+  $ret = ucfirst (lc ($type));
+
+  $ret =~ s/[^A-Za-z_]//g;
+  $ret =~ s/_([A-Za-z])/\U$1\E/g;
+
+  return ("Collectd::Graph::Type::$ret");
+} # type_to_module_name
+
+sub epoch_to_rfc1123
+{
+  my @days = (qw(Sun Mon Tue Wed Thu Fri Sat));
+  my @months = (qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec));
+
+  my $epoch = @_ ? shift : time ();
+  my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($epoch);
+  my $string = sprintf ('%s, %02d %s %4d %02d:%02d:%02d GMT', $days[$wday], $mday,
+      $months[$mon], 1900 + $year, $hour ,$min, $sec);
+  return ($string);
+}
+
+sub flush_files
+{
+  my $all_files = shift;
+  my %opts = @_;
+
+  my $begin;
+  my $end;
+  my $addr;
+  my $interval;
+  my $sock;
+  my $now;
+  my $files_to_flush = [];
+  my $status;
+
+  if (!defined $opts{'begin'})
+  {
+    cluck ("begin is not defined");
+    return;
+  }
+  $begin = $opts{'begin'};
+
+  if (!defined $opts{'end'})
+  {
+    cluck ("end is not defined");
+    return;
+  }
+  $end = $opts{'end'};
+
+  if (!$opts{'addr'})
+  {
+    return (1);
+  }
+
+  $interval = $opts{'interval'} || 10;
+
+  if (ref ($all_files) eq 'HASH')
+  {
+    my @tmp = ($all_files);
+    $all_files = \@tmp;
+  }
+
+  $now = time ();
+  # Don't flush anything if the timespan is in the future.
+  if (($end > $now) && ($begin > $now))
+  {
+    return (1);
+  }
+
+  for (@$all_files)
+  {
+    my $file_orig = $_;
+    my $file_name = ident_to_filename ($file_orig);
+    my $file_copy = {};
+    my @statbuf;
+    my $mtime;
+
+    @statbuf = stat ($file_name);
+    if (!@statbuf)
+    {
+      next;
+    }
+    $mtime = $statbuf[9];
+
+    # Skip if file is fresh
+    if (($now - $mtime) <= $interval)
+    {
+      next;
+    }
+    # or $end is before $mtime
+    elsif (($end != 0) && (($end - $mtime) <= 0))
+    {
+      next;
+    }
+
+    $file_copy->{'host'} = $file_orig->{'hostname'};
+    $file_copy->{'plugin'} = $file_orig->{'plugin'};
+    if (exists $file_orig->{'plugin_instance'})
+    {
+      $file_copy->{'plugin_instance'} = $file_orig->{'plugin_instance'}
+    }
+    $file_copy->{'type'} = $file_orig->{'type'};
+    if (exists $file_orig->{'type_instance'})
+    {
+      $file_copy->{'type_instance'} = $file_orig->{'type_instance'}
+    }
+
+    push (@$files_to_flush, $file_copy);
+  } # for (@$all_files)
+
+  if (!@$files_to_flush)
+  {
+    return (1);
+  }
+
+  $sock = Collectd::Unixsock->new ($opts{'addr'});
+  if (!$sock)
+  {
+    return;
+  }
+
+  $status = $sock->flush (plugins => ['rrdtool'], identifier => $files_to_flush);
+  if (!$status)
+  {
+    cluck ("FLUSH failed: " . $sock->{'error'});
+    $sock->destroy ();
+    return;
+  }
+
+  $sock->destroy ();
+  return (1);
+} # flush_files
+
+# vim: set shiftwidth=2 softtabstop=2 tabstop=8 :
diff --git a/contrib/collection3/lib/Collectd/Graph/Config.pm b/contrib/collection3/lib/Collectd/Graph/Config.pm
new file mode 100644 (file)
index 0000000..d20be35
--- /dev/null
@@ -0,0 +1,144 @@
+package Collectd::Graph::Config;
+
+=head1 NAME
+
+Collectd::Graph::Config - Parse the collection3 config file.
+
+=cut
+
+# Copyright (C) 2008  Florian octo Forster <octo at verplant.org>
+#
+# 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; only version 2 of the License is applicable.
+#
+# 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.
+
+use strict;
+use warnings;
+
+use Carp (qw(cluck confess));
+use Exporter ();
+use Config::General ('ParseConfig');
+use Collectd::Graph::Type ();
+
+@Collectd::Graph::Config::ISA = ('Exporter');
+@Collectd::Graph::Config::EXPORT_OK = (qw(gc_read_config gc_get_config
+  gc_get_scalar));
+
+our $Configuration = undef;
+
+return (1);
+
+=head1 EXPORTED FUNCTIONS
+
+=over 4
+
+=item B<gc_read_config> (I<$file>)
+
+Reads the configuration from the file located at I<$file>. Returns B<true> when
+successfull and B<false> otherwise.
+
+=cut
+
+sub gc_read_config
+{
+  my $file = shift;
+  my %conf;
+
+  if ($Configuration)
+  {
+    return (1);
+  }
+
+  $file ||= "etc/collection.conf";
+
+  %conf = ParseConfig (-ConfigFile => $file,
+    -LowerCaseNames => 1,
+    -UseApacheInclude => 1,
+    -IncludeDirectories => 1,
+    ($Config::General::VERSION >= 2.38) ? (-IncludeAgain => 0) : (),
+    -MergeDuplicateBlocks => 1,
+    -CComments => 0);
+  if (!%conf)
+  {
+    return;
+  }
+
+  $Configuration = \%conf;
+  return (1);
+} # gc_read_config
+
+=item B<gc_get_config> ()
+
+Returns the hash as provided by L<Config::General>. The hash is returned as a
+hash reference. Don't change it!
+
+=cut
+
+sub gc_get_config
+{
+  return ($Configuration);
+} # gc_get_config
+
+=item B<gc_get_config> (I<$key>, [I<$default>])
+
+Returns the scalar value I<$key> from the config file. If the key does not
+exist, I<$default> will be returned. If no default is given, B<undef> will be
+used in this case.
+
+=cut
+
+sub gc_get_scalar
+{
+  my $key = shift;
+  my $default = (@_ != 0) ? shift : undef;
+  my $value;
+
+  if (!$Configuration)
+  {
+    return ($default);
+  }
+
+  $value = $Configuration->{lc ($key)};
+  if (!defined ($value))
+  {
+    return ($default);
+  }
+
+  if (ref ($value) ne '')
+  {
+    cluck ("Value for `$key' should be scalar, but actually is "
+      . ref ($value));
+    return ($default);
+  }
+
+  return ($value);
+} # gc_get_config
+
+=back
+
+=head1 DEPENDS ON
+
+L<Config::General>
+
+=head1 SEE ALSO
+
+L<Collectd::Graph::Type>
+
+=head1 AUTHOR AND LICENSE
+
+Copyright (c) 2008 by Florian Forster
+E<lt>octoE<nbsp>atE<nbsp>verplant.orgE<gt>. Licensed under the terms of the GNU
+General Public License, VersionE<nbsp>2 (GPLv2).
+
+=cut
+
+# vim: set shiftwidth=2 softtabstop=2 tabstop=8 et fdm=marker :
diff --git a/contrib/collection3/lib/Collectd/Graph/Type.pm b/contrib/collection3/lib/Collectd/Graph/Type.pm
new file mode 100644 (file)
index 0000000..1fb60af
--- /dev/null
@@ -0,0 +1,495 @@
+package Collectd::Graph::Type;
+
+=head1 NAME
+
+Collectd::Graph::Type - Base class for the collectd graphing infrastructure
+
+=cut
+
+# Copyright (C) 2008  Florian octo Forster <octo at verplant.org>
+#
+# 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; only version 2 of the License is applicable.
+#
+# 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.
+
+use strict;
+use warnings;
+
+use Carp (qw(confess cluck));
+use RRDs ();
+use URI::Escape (qw(uri_escape));
+
+use Collectd::Graph::Common (qw($ColorCanvas $ColorFullBlue $ColorHalfBlue
+  ident_to_filename
+  ident_to_string
+  get_faded_color));
+
+return (1);
+
+=head1 DESCRIPTION
+
+This module serves as base class for more specialized classes realizing
+specific "types".
+
+=head1 MEMBER VARIABLES
+
+As typical in Perl, a Collectd::Graph::Type object is a blessed hash reference.
+Member variables are entries in that hash. Inheriting classes are free to add
+additional entries. To set the member variable B<foo> to B<42>, do:
+
+ $obj->{'foo'} = 42;
+
+The following members control the behavior of Collectd::Graph::Type.
+
+=over 4
+
+=item B<files> (array reference)
+
+List of RRD files. Each file is passed as "ident", i.E<nbsp>e. broken up into
+"hostname", "plugin", "type" and optionally "plugin_instance" and
+"type_instance". Use the B<addFiles> method rather than setting this directly.
+
+=item B<data_sources> (array reference)
+
+List of data sources in the RRD files. If this is not given, the default
+implementation of B<getDataSources> will use B<RRDs::info> to find out which
+data sources are contained in the files.
+
+=item B<ds_names> (array reference)
+
+Names of the data sources as printed in the graph. Should be in the same order
+as the data sources are returned by B<getDataSources>.
+
+=item B<rrd_title> (string)
+
+Title of the RRD graph. The title can contain "{hostname}", "{plugin}" and so
+on which are replaced with their actual value. See the B<getTitle> method
+below.
+
+=item B<rrd_opts> (array reference)
+
+List of options directly passed to B<RRDs::graph>.
+
+=item B<rrd_format> (string)
+
+Format to use with B<GPRINT>. Defaults to C<%5.1lf>.
+
+=item B<rrd_colors> (hash reference)
+
+Mapping of data source names to colors, used when graphing the different data
+sources.  Colors are given in the typical hexadecimal RGB form, but without
+leading "#", e.E<nbsp>g.:
+
+ $obj->{'rrd_colors'} = {foo => 'ff0000', bar => '00ff00'};
+
+=back
+
+=head1 METHODS
+
+The following methods are used by the graphing front end and may be overwritten
+to customize their behavior.
+
+=over 4
+
+=cut
+
+sub _get_ds_from_file
+{
+  my $file = shift;
+  my $info = RRDs::info ($file);
+  my %ds = ();
+  my @ds = ();
+
+  if (!$info || (ref ($info) ne 'HASH'))
+  {
+    return;
+  }
+
+  for (keys %$info)
+  {
+    if (m/^ds\[([^\]]+)\]/)
+    {
+      $ds{$1} = 1;
+    }
+  }
+
+  @ds = (keys %ds);
+  if (wantarray ())
+  {
+    return (@ds);
+  }
+  elsif (@ds)
+  {
+    return (\@ds);
+  }
+  else
+  {
+    return;
+  }
+} # _get_ds_from_file
+
+sub new
+{
+  my $pkg = shift;
+  my $obj = bless ({files => []}, $pkg);
+
+  if (@_)
+  {
+    $obj->addFiles (@_);
+  }
+
+  return ($obj);
+}
+
+=item B<addFiles> ({ I<ident> }, [...])
+
+Adds the given idents (which are hash references) to the B<files> member
+variable, see above.
+
+=cut
+
+sub addFiles
+{
+  my $obj = shift;
+  push (@{$obj->{'files'}}, @_);
+}
+
+=item B<getGraphsNum> ()
+
+Returns the number of graphs that can be generated from the added files. By
+default this number equals the number of files.
+
+=cut
+
+sub getGraphsNum
+{
+  my $obj = shift;
+  return (scalar @{$obj->{'files'}});
+}
+
+=item B<getDataSources> ()
+
+Returns the names of the data sources. If the B<data_sources> member variable
+is unset B<RRDs::info> is used to read that information from the first file.
+Set the B<data_sources> member variable instead of overloading this method!
+
+=cut
+
+sub getDataSources
+{
+  my $obj = shift;
+
+  if (!defined $obj->{'data_sources'})
+  {
+    my $ident;
+    my $filename;
+
+    if (!@{$obj->{'files'}})
+    {
+      return;
+    }
+
+    $ident = $obj->{'files'}[0];
+    $filename = ident_to_filename ($ident);
+
+    $obj->{'data_sources'} = _get_ds_from_file ($filename);
+    if (!$obj->{'data_sources'})
+    {
+      cluck ("_get_ds_from_file ($filename) failed.");
+    }
+  }
+
+  if (!defined $obj->{'data_sources'})
+  {
+    return;
+  }
+  elsif (wantarray ())
+  {
+    return (@{$obj->{'data_sources'}})
+  }
+  else
+  {
+    $obj->{'data_sources'};
+  }
+} # getDataSources
+
+
+=item B<getTitle> (I<$index>)
+
+Returns the title of the I<$index>th B<graph> (not necessarily file!). If the
+B<rrd_title> member variable is unset, a generic title is generated from the
+ident. Otherwise the substrings "{hostname}", "{plugin}", "{plugin_instance}",
+"{type}", and "{type_instance}" are replaced by their respective values.
+
+=cut
+
+sub getTitle
+{
+  my $obj = shift;
+  my $ident = shift;
+  my $title = $obj->{'rrd_title'};
+
+  if (!$title)
+  {
+    return (ident_to_string ($ident));
+  }
+
+  my $hostname = $ident->{'hostname'};
+  my $plugin = $ident->{'plugin'};
+  my $plugin_instance = $ident->{'plugin_instance'};
+  my $type = $ident->{'type'};
+  my $type_instance = $ident->{'type_instance'};
+  my $instance;
+
+  if (defined $type_instance)
+  {
+    $instance = $type_instance;
+  }
+  elsif (defined $plugin_instance)
+  {
+    $instance = $plugin_instance;
+  }
+  else
+  {
+    $instance = 'no instance';
+  }
+
+  if (!defined $plugin_instance)
+  {
+    $plugin_instance = 'no instance';
+  }
+
+  if (!defined $type_instance)
+  {
+    $type_instance = 'no instance';
+  }
+
+  $title =~ s#{hostname}#$hostname#g;
+  $title =~ s#{plugin}#$plugin#g;
+  $title =~ s#{plugin_instance}#$plugin_instance#g;
+  $title =~ s#{type}#$type#g;
+  $title =~ s#{type_instance}#$type_instance#g;
+  $title =~ s#{instance}#$instance#g;
+
+  return ($title);
+}
+
+=item B<getRRDArgs> (I<$index>)
+
+Return the arguments needed to generate the graph from the RRD file(s). If the
+file has only one data source, this default implementation will generate that
+typical min, average, max graph you probably know from temperatures and such.
+If the RRD files have multiple data sources, the average of each data source is
+printes as simple line.
+
+=cut
+
+sub getRRDArgs
+{
+  my $obj = shift;
+  my $index = shift;
+
+  my $ident = $obj->{'files'}[$index];
+  if (!$ident)
+  {
+    cluck ("Invalid index: $index");
+    return;
+  }
+  my $filename = ident_to_filename ($ident);
+
+  my $rrd_opts = $obj->{'rrd_opts'} || [];
+  my $rrd_title = $obj->getTitle ($ident);
+  my $format = $obj->{'rrd_format'} || '%5.1lf';
+
+  my $rrd_colors = $obj->{'rrd_colors'};
+  my @ret = ('-t', $rrd_title, @$rrd_opts);
+
+  if (defined $obj->{'rrd_vertical'})
+  {
+    push (@ret, '-v', $obj->{'rrd_vertical'});
+  }
+
+  my $ds_names = $obj->{'ds_names'};
+  if (!$ds_names)
+  {
+    $ds_names = {};
+  }
+
+  my $ds = $obj->getDataSources ();
+  if (!$ds)
+  {
+    confess ("obj->getDataSources failed.");
+  }
+
+  if (!$rrd_colors)
+  {
+    my @tmp = ('0000ff', 'ff0000', '00ff00', 'ff00ff', '00ffff', 'ffff00');
+
+    for (my $i = 0; $i < @$ds; $i++)
+    {
+      $rrd_colors->{$ds->[$i]} = $tmp[$i % @tmp];
+    }
+  }
+
+  for (my $i = 0; $i < @$ds; $i++)
+  {
+    my $f = $filename;
+    my $ds_name = $ds->[$i];
+
+    # We need to escape colons for RRDTool..
+    $f =~ s#:#\\:#g;
+    $ds_name =~ s#:#\\:#g;
+
+    push (@ret,
+      "DEF:min${i}=${f}:${ds_name}:MIN",
+      "DEF:avg${i}=${f}:${ds_name}:AVERAGE",
+      "DEF:max${i}=${f}:${ds_name}:MAX");
+  }
+
+  if (@$ds == 1)
+  {
+    my $ds_name = $ds->[0];
+    my $color_fg = $rrd_colors->{$ds_name} || '000000';
+    my $color_bg = get_faded_color ($color_fg);
+
+    if ($ds_names->{$ds_name})
+    {
+      $ds_name = $ds_names->{$ds_name};
+    }
+    $ds_name =~ s#:#\\:#g;
+
+    push (@ret, 
+      "AREA:max0#${color_bg}",
+      "AREA:min0#${ColorCanvas}",
+      "LINE1:avg0#${color_fg}:${ds_name}",
+      "GPRINT:min0:MIN:${format} Min,",
+      "GPRINT:avg0:AVERAGE:${format} Avg,",
+      "GPRINT:max0:MAX:${format} Max,",
+      "GPRINT:avg0:LAST:${format} Last\\l");
+  }
+  else
+  {
+    for (my $i = 0; $i < @$ds; $i++)
+    {
+      my $ds_name = $ds->[$i];
+      my $color = $rrd_colors->{$ds_name} || '000000';
+
+      if ($ds_names->{$ds_name})
+      {
+       $ds_name = $ds_names->{$ds_name};
+      }
+
+      push (@ret, 
+       "LINE1:avg${i}#${color}:${ds_name}",
+       "GPRINT:min${i}:MIN:${format} Min,",
+       "GPRINT:avg${i}:AVERAGE:${format} Avg,",
+       "GPRINT:max${i}:MAX:${format} Max,",
+       "GPRINT:avg${i}:LAST:${format} Last\\l");
+    }
+  }
+
+  return (\@ret);
+} # getRRDArgs
+
+=item B<getGraphArgs> (I<$index>)
+
+Returns the parameters that should be passed to the CGI script to generate the
+I<$index>th graph. The returned string is already URI-encoded and will possibly
+set the "hostname", "plugin", "plugin_instance", "type", and "type_instance"
+parameters.
+
+The default implementation simply uses the ident of the I<$index>th file to
+fill this.
+
+=cut
+
+sub getGraphArgs
+{
+  my $obj = shift;
+  my $index = shift;
+  my $ident = $obj->{'files'}[$index];
+
+  my @args = ();
+  for (qw(hostname plugin plugin_instance type type_instance))
+  {
+    if (defined ($ident->{$_}))
+    {
+      push (@args, uri_escape ($_) . '=' . uri_escape ($ident->{$_}));
+    }
+  }
+
+  return (join (';', @args));
+}
+
+=item B<getLastModified> ([I<$index>])
+
+If I<$index> is not given, the modification time of all files is scanned and the most recent modification is returned. If I<$index> is given, only the files belonging to the I<$index>th graph will be considered.
+
+=cut
+
+sub getLastModified
+{
+  my $obj = shift;
+  my $index = @_ ? shift : -1;
+
+  my $mtime = 0;
+
+  if ($index == -1)
+  {
+    for (@{$obj->{'files'}})
+    {
+      my $ident = $_;
+      my $filename = ident_to_filename ($ident);
+      my @statbuf = stat ($filename);
+
+      if (!@statbuf)
+      {
+       next;
+      }
+
+      if ($mtime < $statbuf[9])
+      {
+       $mtime = $statbuf[9];
+      }
+    }
+  }
+  else
+  {
+    my $ident = $obj->{'files'}[$index];
+    my $filename = ident_to_filename ($ident);
+    my @statbuf = stat ($filename);
+
+    $mtime = $statbuf[9];
+  }
+
+  if (!$mtime)
+  {
+    return;
+  }
+  return ($mtime);
+} # getLastModified
+
+=back
+
+=head1 SEE ALSO
+
+L<Collectd::Graph::Type::GenericStacked>
+
+=head1 AUTHOR AND LICENSE
+
+Copyright (c) 2008 by Florian Forster
+E<lt>octoE<nbsp>atE<nbsp>verplant.orgE<gt>. Licensed under the terms of the GNU
+General Public License, VersionE<nbsp>2 (GPLv2).
+
+=cut
+
+# vim: set shiftwidth=2 softtabstop=2 tabstop=8 :
diff --git a/contrib/collection3/lib/Collectd/Graph/Type/Df.pm b/contrib/collection3/lib/Collectd/Graph/Type/Df.pm
new file mode 100644 (file)
index 0000000..b4eb8b1
--- /dev/null
@@ -0,0 +1,68 @@
+package Collectd::Graph::Type::Df;
+
+use strict;
+use warnings;
+use base ('Collectd::Graph::Type');
+
+use Collectd::Graph::Common (qw(ident_to_filename get_faded_color));
+
+return (1);
+
+sub getDataSources
+{
+  return ([qw(free used)]);
+} # getDataSources
+
+sub new
+{
+  my $pkg = shift;
+  my $obj = Collectd::Graph::Type->new (@_);
+  $obj->{'data_sources'} = [qw(free used)];
+  $obj->{'rrd_opts'} = ['-v', 'Bytes'];
+  $obj->{'rrd_title'} = 'Disk space ({type_instance})';
+  $obj->{'rrd_format'} = '%5.1lf%sB';
+  $obj->{'colors'} = [qw(00b000 ff0000)];
+
+  return (bless ($obj, $pkg));
+} # new
+
+sub getRRDArgs
+{
+  my $obj = shift;
+  my $index = shift;
+
+  my $ident = $obj->{'files'}[$index];
+  if (!$ident)
+  {
+    cluck ("Invalid index: $index");
+    return;
+  }
+  my $filename = ident_to_filename ($ident);
+  $filename =~ s#:#\\:#g;
+
+  my $faded_green = get_faded_color ('00ff00');
+  my $faded_red = get_faded_color ('ff0000');
+
+  return (['-t', 'Free space (' . $ident->{'type_instance'} . ')', '-v', 'Bytes', '-l', '0',
+    "DEF:free_min=${filename}:free:MIN",
+    "DEF:free_avg=${filename}:free:AVERAGE",
+    "DEF:free_max=${filename}:free:MAX",
+    "DEF:used_min=${filename}:used:MIN",
+    "DEF:used_avg=${filename}:used:AVERAGE",
+    "DEF:used_max=${filename}:used:MAX",
+    "CDEF:both_avg=free_avg,used_avg,+",
+    "AREA:both_avg#${faded_green}",
+    "AREA:used_avg#${faded_red}",
+    'LINE1:both_avg#00ff00:Free',
+    'GPRINT:free_min:MIN:%5.1lf%sB Min,',
+    'GPRINT:free_avg:AVERAGE:%5.1lf%sB Avg,',
+    'GPRINT:free_max:MAX:%5.1lf%sB Max,',
+    'GPRINT:free_avg:LAST:%5.1lf%sB Last\l',
+    'LINE1:used_avg#ff0000:Used',
+    'GPRINT:used_min:MIN:%5.1lf%sB Min,',
+    'GPRINT:used_avg:AVERAGE:%5.1lf%sB Avg,',
+    'GPRINT:used_max:MAX:%5.1lf%sB Max,',
+    'GPRINT:used_avg:LAST:%5.1lf%sB Last\l']);
+} # getRRDArgs
+
+# vim: set shiftwidth=2 softtabstop=2 tabstop=8 :
diff --git a/contrib/collection3/lib/Collectd/Graph/Type/GenericIO.pm b/contrib/collection3/lib/Collectd/Graph/Type/GenericIO.pm
new file mode 100644 (file)
index 0000000..58b7566
--- /dev/null
@@ -0,0 +1,113 @@
+package Collectd::Graph::Type::GenericIO;
+
+use strict;
+use warnings;
+use base ('Collectd::Graph::Type');
+
+use Carp ('confess');
+
+use Collectd::Graph::Common (qw($ColorCanvas ident_to_filename get_faded_color));
+
+return (1);
+
+sub getRRDArgs
+{
+  my $obj = shift;
+  my $index = shift;
+
+  my $ident = $obj->{'files'}[$index] || confess ("Unknown index $index");
+  my $filename = ident_to_filename ($ident);
+
+  my $rrd_opts = $obj->{'rrd_opts'} || [];
+  my $rrd_title = $obj->getTitle ($ident);
+  my $format = $obj->{'rrd_format'} || '%5.1lf%s';
+
+  my $ds_list = $obj->getDataSources ();
+  my $ds_names = $obj->{'ds_names'};
+  if (!$ds_names)
+  {
+    $ds_names = {};
+  }
+
+  my $colors = $obj->{'rrd_colors'} || {};
+  my @ret = ('-t', $rrd_title, @$rrd_opts);
+
+  if (defined $obj->{'rrd_vertical'})
+  {
+    push (@ret, '-v', $obj->{'rrd_vertical'});
+  }
+
+  if (@$ds_list != 2)
+  {
+    my $num = 0 + @$ds_list;
+    confess ("Expected two data sources, but there is/are $num");
+  }
+
+  my $rx_ds = $ds_list->[0];
+  my $tx_ds = $ds_list->[1];
+
+  my $rx_ds_name = $ds_names->{$rx_ds} || $rx_ds;
+  my $tx_ds_name = $ds_names->{$tx_ds} || $tx_ds;
+
+  my $rx_color_fg = $colors->{$rx_ds} || '0000ff';
+  my $tx_color_fg = $colors->{$tx_ds} || '00b000';
+
+  my $rx_color_bg = get_faded_color ($rx_color_fg);
+  my $tx_color_bg = get_faded_color ($tx_color_fg);
+  my $overlap_color = get_faded_color ($rx_color_bg, background => $tx_color_bg);
+
+  $filename =~ s#:#\\:#g;
+  $rx_ds =~ s#:#\\:#g;
+  $tx_ds =~ s#:#\\:#g;
+  $rx_ds_name =~ s#:#\\:#g;
+  $tx_ds_name =~ s#:#\\:#g;
+
+  if ($obj->{'scale'})
+  {
+    my $factor = $obj->{'scale'};
+
+    push (@ret,
+       "DEF:min_rx_raw=${filename}:${rx_ds}:MIN",
+       "DEF:avg_rx_raw=${filename}:${rx_ds}:AVERAGE",
+       "DEF:max_rx_raw=${filename}:${rx_ds}:MAX",
+       "DEF:min_tx_raw=${filename}:${tx_ds}:MIN",
+       "DEF:avg_tx_raw=${filename}:${tx_ds}:AVERAGE",
+       "DEF:max_tx_raw=${filename}:${tx_ds}:MAX",
+       "CDEF:min_rx=min_rx_raw,${factor},*",
+       "CDEF:avg_rx=avg_rx_raw,${factor},*",
+       "CDEF:max_rx=max_rx_raw,${factor},*",
+       "CDEF:min_tx=min_tx_raw,${factor},*",
+       "CDEF:avg_tx=avg_tx_raw,${factor},*",
+       "CDEF:max_tx=max_tx_raw,${factor},*");
+  }
+  else # (!$obj->{'scale'})
+  {
+    push (@ret,
+       "DEF:min_rx=${filename}:${rx_ds}:MIN",
+       "DEF:avg_rx=${filename}:${rx_ds}:AVERAGE",
+       "DEF:max_rx=${filename}:${rx_ds}:MAX",
+       "DEF:min_tx=${filename}:${tx_ds}:MIN",
+       "DEF:avg_tx=${filename}:${tx_ds}:AVERAGE",
+       "DEF:max_tx=${filename}:${tx_ds}:MAX");
+  }
+
+  push (@ret,
+      "CDEF:overlap=avg_rx,avg_tx,LT,avg_rx,avg_tx,IF",
+      "AREA:avg_rx#${rx_color_bg}",
+      "AREA:avg_tx#${tx_color_bg}",
+      "AREA:overlap#${overlap_color}",
+      "LINE1:avg_rx#${rx_color_fg}:${rx_ds_name}",
+      "GPRINT:min_rx:MIN:${format} Min,",
+      "GPRINT:avg_rx:AVERAGE:${format} Avg,",
+      "GPRINT:max_rx:MAX:${format} Max,",
+      "GPRINT:avg_rx:LAST:${format} Last\\l",
+      "LINE1:avg_tx#${tx_color_fg}:${tx_ds_name}",
+      "GPRINT:min_tx:MIN:${format} Min,",
+      "GPRINT:avg_tx:AVERAGE:${format} Avg,",
+      "GPRINT:max_tx:MAX:${format} Max,",
+      "GPRINT:avg_tx:LAST:${format} Last\\l");
+
+  return (\@ret);
+} # getRRDArgs
+
+# vim: set shiftwidth=2 softtabstop=2 tabstop=8 :
diff --git a/contrib/collection3/lib/Collectd/Graph/Type/GenericStacked.pm b/contrib/collection3/lib/Collectd/Graph/Type/GenericStacked.pm
new file mode 100644 (file)
index 0000000..a1a3e1c
--- /dev/null
@@ -0,0 +1,157 @@
+package Collectd::Graph::Type::GenericStacked;
+
+use strict;
+use warnings;
+use base ('Collectd::Graph::Type');
+
+use Collectd::Graph::Common (qw($ColorCanvas $ColorFullBlue $ColorHalfBlue
+  group_files_by_plugin_instance ident_to_filename sanitize_type_instance
+  get_faded_color sort_idents_by_type_instance));
+
+return (1);
+
+sub getGraphsNum
+{
+  my $obj = shift;
+  my $group = group_files_by_plugin_instance (@{$obj->{'files'}});
+
+  return (scalar (keys %$group));
+}
+
+sub getRRDArgs
+{
+  my $obj = shift;
+  my $index = shift;
+
+  my $group = group_files_by_plugin_instance (@{$obj->{'files'}});
+  my @group = sort (keys %$group);
+
+  my $rrd_opts = $obj->{'rrd_opts'} || [];
+  my $format = $obj->{'rrd_format'} || '%5.1lf';
+
+  my $idents = $group->{$group[$index]};
+  my $ds_name_len = 0;
+
+  my $ds = $obj->getDataSources ();
+  if (!$ds)
+  {
+    confess ("obj->getDataSources failed.");
+  }
+  if (@$ds != 1)
+  {
+    confess ("I can only work with RRD files that have "
+      . "exactly one data source!");
+  }
+  my $data_source = $ds->[0];
+
+  my $rrd_title = $obj->getTitle ($idents->[0]);
+
+  my $colors = $obj->{'rrd_colors'} || {};
+  my @ret = ('-t', $rrd_title, @$rrd_opts);
+
+  if (defined $obj->{'rrd_vertical'})
+  {
+    push (@ret, '-v', $obj->{'rrd_vertical'});
+  }
+
+  if ($obj->{'custom_order'})
+  {
+    sort_idents_by_type_instance ($idents, $obj->{'custom_order'});
+  }
+
+  $obj->{'ds_names'} ||= {};
+  my @names = map { $obj->{'ds_names'}{$_->{'type_instance'}} || $_->{'type_instance'} } (@$idents);
+
+  for (my $i = 0; $i < @$idents; $i++)
+  {
+    my $ident = $idents->[$i];
+    my $filename = ident_to_filename ($ident);
+
+    if ($ds_name_len < length ($names[$i]))
+    {
+      $ds_name_len = length ($names[$i]);
+    }
+    
+    # Escape colons _after_ the length has been checked.
+    $names[$i] =~ s/:/\\:/g;
+
+    push (@ret,
+      "DEF:min${i}=${filename}:${data_source}:MIN",
+      "DEF:avg${i}=${filename}:${data_source}:AVERAGE",
+      "DEF:max${i}=${filename}:${data_source}:MAX");
+  }
+
+  for (my $i = @$idents - 1; $i >= 0; $i--)
+  {
+    if ($i == (@$idents - 1))
+    {
+      push (@ret,
+       "CDEF:cdef${i}=avg${i}");
+    }
+    else
+    {
+      my $j = $i + 1;
+      push (@ret,
+       "CDEF:cdef${i}=cdef${j},avg${i},+");
+    }
+  }
+
+  for (my $i = 0; $i < @$idents; $i++)
+  {
+    my $type_instance = $idents->[$i]{'type_instance'};
+    my $color = '000000';
+    if (exists $colors->{$type_instance})
+    {
+      $color = $colors->{$type_instance};
+    }
+
+    $color = get_faded_color ($color);
+
+    push (@ret,
+      "AREA:cdef${i}#${color}");
+  }
+
+  for (my $i = 0; $i < @$idents; $i++)
+  {
+    my $type_instance = $idents->[$i]{'type_instance'};
+    my $ds_name = sprintf ("%-*s", $ds_name_len, $names[$i]);
+    my $color = '000000';
+    if (exists $colors->{$type_instance})
+    {
+      $color = $colors->{$type_instance};
+    }
+    push (@ret,
+      "LINE1:cdef${i}#${color}:${ds_name}",
+      "GPRINT:min${i}:MIN:${format} Min,",
+      "GPRINT:avg${i}:AVERAGE:${format} Avg,",
+      "GPRINT:max${i}:MAX:${format} Max,",
+      "GPRINT:avg${i}:LAST:${format} Last\\l");
+  }
+
+  return (\@ret);
+}
+
+sub getGraphArgs
+{
+  my $obj = shift;
+  my $index = shift;
+
+  my $group = group_files_by_plugin_instance (@{$obj->{'files'}});
+  my @group = sort (keys %$group);
+
+  my $idents = $group->{$group[$index]};
+
+  my @args = ();
+  for (qw(hostname plugin plugin_instance type))
+  {
+    if (defined ($idents->[0]{$_}))
+    {
+      push (@args, $_ . '=' . $idents->[0]{$_});
+    }
+  }
+
+  return (join (';', @args));
+} # getGraphArgs
+
+
+# vim: set shiftwidth=2 softtabstop=2 tabstop=8 :
diff --git a/contrib/collection3/lib/Collectd/Graph/Type/Load.pm b/contrib/collection3/lib/Collectd/Graph/Type/Load.pm
new file mode 100644 (file)
index 0000000..b6ca4a6
--- /dev/null
@@ -0,0 +1,69 @@
+package Collectd::Graph::Type::Load;
+
+use strict;
+use warnings;
+use base ('Collectd::Graph::Type');
+
+use Collectd::Graph::Common (qw($ColorCanvas ident_to_filename get_faded_color));
+
+return (1);
+
+sub new
+{
+  my $pkg = shift;
+  my $obj = Collectd::Graph::Type->new (@_);
+  $obj->{'data_sources'} = [qw(shortterm midterm longterm)];
+  $obj->{'rrd_opts'} = ['-v', 'System load'];
+  $obj->{'rrd_title'} = 'System load';
+  $obj->{'rrd_format'} = '%.2lf';
+  $obj->{'colors'} = [qw(00ff00 0000ff ff0000)];
+
+  return (bless ($obj, $pkg));
+} # new
+
+sub getRRDArgs
+{
+  my $obj = shift;
+  my $index = shift;
+
+  my $ident = $obj->{'files'}[$index];
+  if (!$ident)
+  {
+    cluck ("Invalid index: $index");
+    return;
+  }
+  my $filename = ident_to_filename ($ident);
+  $filename =~ s#:#\\:#g;
+
+  my $faded_green = get_faded_color ('00ff00');
+
+  return (['-t', 'System load', '-v', 'System load',
+    "DEF:s_min=${filename}:shortterm:MIN",
+    "DEF:s_avg=${filename}:shortterm:AVERAGE",
+    "DEF:s_max=${filename}:shortterm:MAX",
+    "DEF:m_min=${filename}:midterm:MIN",
+    "DEF:m_avg=${filename}:midterm:AVERAGE",
+    "DEF:m_max=${filename}:midterm:MAX",
+    "DEF:l_min=${filename}:longterm:MIN",
+    "DEF:l_avg=${filename}:longterm:AVERAGE",
+    "DEF:l_max=${filename}:longterm:MAX",
+    "AREA:s_max#${faded_green}",
+    "AREA:s_min#${ColorCanvas}",
+    "LINE1:s_avg#00ff00: 1 min",
+    "GPRINT:s_min:MIN:%.2lf Min,",
+    "GPRINT:s_avg:AVERAGE:%.2lf Avg,",
+    "GPRINT:s_max:MAX:%.2lf Max,",
+    "GPRINT:s_avg:LAST:%.2lf Last\\l",
+    "LINE1:m_avg#0000ff: 5 min",
+    "GPRINT:m_min:MIN:%.2lf Min,",
+    "GPRINT:m_avg:AVERAGE:%.2lf Avg,",
+    "GPRINT:m_max:MAX:%.2lf Max,",
+    "GPRINT:m_avg:LAST:%.2lf Last\\l",
+    "LINE1:l_avg#ff0000:15 min",
+    "GPRINT:l_min:MIN:%.2lf Min,",
+    "GPRINT:l_avg:AVERAGE:%.2lf Avg,",
+    "GPRINT:l_max:MAX:%.2lf Max,",
+    "GPRINT:l_avg:LAST:%.2lf Last\\l"]);
+} # sub getRRDArgs
+
+# vim: set shiftwidth=2 softtabstop=2 tabstop=8 :
diff --git a/contrib/collection3/lib/Collectd/Graph/TypeLoader.pm b/contrib/collection3/lib/Collectd/Graph/TypeLoader.pm
new file mode 100644 (file)
index 0000000..c5fe613
--- /dev/null
@@ -0,0 +1,266 @@
+package Collectd::Graph::TypeLoader;
+
+=head1 NAME
+
+Collectd::Graph::TypeLoader - Load a module according to the "type"
+
+=cut
+
+# Copyright (C) 2008  Florian octo Forster <octo at verplant.org>
+#
+# 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; only version 2 of the License is applicable.
+#
+# 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.
+
+use strict;
+use warnings;
+
+use Carp (qw(cluck confess));
+use Exporter ();
+use Config::General ('ParseConfig');
+use Collectd::Graph::Config ('gc_get_config');
+use Collectd::Graph::Type ();
+
+@Collectd::Graph::TypeLoader::ISA = ('Exporter');
+@Collectd::Graph::TypeLoader::EXPORT_OK = ('tl_load_type');
+
+our @ArrayMembers = (qw(data_sources rrd_opts custom_order));
+our @ScalarMembers = (qw(rrd_title rrd_format rrd_vertical scale));
+our @DSMappedMembers = (qw(ds_names rrd_colors));
+
+our %MemberToConfigMap =
+(
+  data_sources => 'datasources',
+  ds_names => 'dsname',
+  rrd_title => 'rrdtitle',
+  rrd_opts => 'rrdoptions',
+  rrd_format => 'rrdformat',
+  rrd_vertical => 'rrdverticallabel',
+  rrd_colors => 'color',
+  scale => 'scale', # GenericIO only
+  custom_order => 'order' # GenericStacked only
+);
+
+return (1);
+
+sub _create_object
+{
+  my $module = shift;
+  my $obj;
+
+  local $SIG{__WARN__} = sub {};
+  local $SIG{__DIE__} = sub {};
+
+  eval <<PERL;
+  require $module;
+  \$obj = ${module}->new ();
+PERL
+  if (!$obj)
+  {
+    return;
+  }
+
+  return ($obj);
+} # _create_object
+
+sub _load_module_from_config
+{
+  my $conf = shift;
+
+  my $module = $conf->{'module'};
+  my $obj;
+  
+  if ($module && !($module =~ m/::/))
+  {
+    $module = "Collectd::Graph::Type::$module";
+  }
+
+  if ($module)
+  {
+    $obj = _create_object ($module);
+    if (!$obj)
+    {
+      cluck ("Creating an $module object failed");
+      return;
+    }
+  }
+  else
+  {
+    $obj = Collectd::Graph::Type->new ();
+    if (!$obj)
+    {
+      cluck ("Creating an Collectd::Graph::Type object failed");
+      return;
+    }
+  }
+
+  for (@ScalarMembers) # {{{
+  {
+    my $member = $_;
+    my $key = $MemberToConfigMap{$member};
+    my $val;
+
+    if (!defined $conf->{$key})
+    {
+      next;
+    }
+    $val = $conf->{$key};
+    
+    if (ref ($val) ne '')
+    {
+      cluck ("Invalid value type for $key: " . ref ($val));
+      next;
+    }
+
+    $obj->{$member} = $val;
+  } # }}}
+
+  for (@ArrayMembers) # {{{
+  {
+    my $member = $_;
+    my $key = $MemberToConfigMap{$member};
+    my $val;
+
+    if (!defined $conf->{$key})
+    {
+      next;
+    }
+    $val = $conf->{$key};
+    
+    if (ref ($val) eq 'ARRAY')
+    {
+      $obj->{$member} = $val;
+    }
+    elsif (ref ($val) eq '')
+    {
+      $obj->{$member} = [split (' ', $val)];
+    }
+    else
+    {
+      cluck ("Invalid value type for $key: " . ref ($val));
+    }
+  } # }}}
+
+  for (@DSMappedMembers) # {{{
+  {
+    my $member = $_;
+    my $key = $MemberToConfigMap{$member};
+    my @val_list;
+
+    if (!defined $conf->{$key})
+    {
+      next;
+    }
+
+    if (ref ($conf->{$key}) eq 'ARRAY')
+    {
+      @val_list = @{$conf->{$key}};
+    }
+    elsif (ref ($conf->{$key}) eq '')
+    {
+      @val_list = ($conf->{$key});
+    }
+    else
+    {
+      cluck ("Invalid value type for $key: " . ref ($conf->{$key}));
+      next;
+    }
+
+    for (@val_list)
+    {
+      my $line = $_;
+      my $ds;
+      my $val;
+
+      if (!defined ($line) || (ref ($line) ne ''))
+      {
+        next;
+      }
+
+      ($ds, $val) = split (' ', $line, 2);
+      if (!$ds || !$val)
+      {
+        next;
+      }
+
+      $obj->{$member} ||= {};
+      $obj->{$member}{$ds} = $val;
+    } # for (@val_list)
+  } # }}} for (@DSMappedMembers)
+
+  return ($obj);
+} # _load_module_from_config
+
+sub _load_module_generic
+{
+  my $type = shift;
+  my $module = ucfirst (lc ($type));
+  my $obj;
+
+  $module =~ s/[^A-Za-z_]//g;
+  $module =~ s/_([A-Za-z])/\U$1\E/g;
+
+  $obj = _create_object ($module);
+  if (!$obj)
+  {
+    $obj = Collectd::Graph::Type->new ();
+    if (!$obj)
+    {
+      cluck ("Creating an Collectd::Graph::Type object failed");
+      return;
+    }
+  }
+
+  return ($obj);
+} # _load_module_generic
+
+=head1 EXPORTED FUNCTIONS
+
+=over 4
+
+=item B<tl_load_type> (I<$type>)
+
+Does whatever is necessary to get an object with which to graph RRD files of
+type I<$type>.
+
+=cut
+
+sub tl_load_type
+{
+  my $type = shift;
+  my $conf = gc_get_config ();
+
+  if (defined ($conf) && defined ($conf->{'type'}{$type}))
+  {
+    return (_load_module_from_config ($conf->{'type'}{$type}));
+  }
+  else
+  {
+    return (_load_module_generic ($type));
+  }
+} # tl_load_type
+
+=back
+
+=head1 SEE ALSO
+
+L<Collectd::Graph::Type::GenericStacked>
+
+=head1 AUTHOR AND LICENSE
+
+Copyright (c) 2008 by Florian Forster
+E<lt>octoE<nbsp>atE<nbsp>verplant.orgE<gt>. Licensed under the terms of the GNU
+General Public License, VersionE<nbsp>2 (GPLv2).
+
+=cut
+
+# vim: set shiftwidth=2 softtabstop=2 tabstop=8 et fdm=marker :
diff --git a/contrib/collection3/share/.htaccess b/contrib/collection3/share/.htaccess
new file mode 100644 (file)
index 0000000..e139ace
--- /dev/null
@@ -0,0 +1,2 @@
+Options -ExecCGI
+SetHandler none
diff --git a/contrib/collection3/share/navigate.js b/contrib/collection3/share/navigate.js
new file mode 100644 (file)
index 0000000..3bfe56e
--- /dev/null
@@ -0,0 +1,300 @@
+function nav_init (time_begin, time_end)
+{
+  var all_images;
+  var i;
+
+  all_images = document.getElementsByTagName ("img");
+  for (i = 0; i < all_images.length; i++)
+  {
+    if (all_images[i].className != "graph_image")
+      continue;
+
+    all_images[i].navTimeBegin = new Number (time_begin);
+    all_images[i].navTimeEnd   = new Number (time_end);
+
+    all_images[i].navBaseURL = all_images[i].src.replace (/;(begin|end)=[^;]*/g, '');
+
+    if (all_images[i].addEventListener) /* Mozilla */
+    {
+      all_images[i].addEventListener ('dblclick', nav_handle_dblclick,
+          false /* == bubbling */);
+      all_images[i].addEventListener ('DOMMouseScroll', nav_handle_wheel,
+          false /* == bubbling */);
+    }
+    else
+    {
+      all_images[i].ondblclick = nav_handle_dblclick;
+      all_images[i].onmousewheel = nav_handle_wheel;
+    }
+  }
+
+  return (true);
+} /* nav_init */
+
+function nav_image_repaint (img)
+{
+  if (!img || !img.navBaseURL
+      || !img.navTimeBegin || !img.navTimeEnd)
+    return;
+
+  img.src = img.navBaseURL + ";"
+    + "begin=" + img.navTimeBegin.toFixed (0) + ";"
+    + "end=" + img.navTimeEnd.toFixed (0);
+} /* nav_image_repaint */
+
+function nav_time_reset (img_id ,diff)
+{
+  var img;
+
+  img = document.getElementById (img_id);
+  if (!img)
+    return (false);
+
+  img.navTimeEnd = new Number ((new Date ()).getTime () / 1000);
+  img.navTimeBegin = new Number (img.navTimeEnd - diff);
+
+  nav_image_repaint (img);
+
+  return (true);
+}
+
+function nav_time_change_obj (img, factor_begin, factor_end)
+{
+  var diff;
+
+  if (!img || !img.navBaseURL
+      || !img.navTimeBegin || !img.navTimeEnd)
+    return (false);
+
+  diff = img.navTimeEnd - img.navTimeBegin;
+
+  /* Prevent zooming in if diff is less than five minutes */
+  if ((diff <= 300) && (factor_begin > 0.0) && (factor_end < 0.0))
+    return (true);
+
+  img.navTimeBegin += (diff * factor_begin);
+  img.navTimeEnd   += (diff * factor_end);
+
+  nav_image_repaint (img);
+
+  return (true);
+} /* nav_time_change */
+
+function nav_time_change (img_id, factor_begin, factor_end)
+{
+  var diff;
+
+  if (img_id == '*')
+  {
+    var all_images;
+    var i;
+
+    all_images = document.getElementsByTagName ("img");
+    for (i = 0; i < all_images.length; i++)
+    {
+      if (all_images[i].className != "graph_image")
+        continue;
+    
+      nav_time_change_obj (all_images[i], factor_begin, factor_end);
+    }
+  }
+  else
+  {
+    var img;
+
+    img = document.getElementById (img_id);
+    if (!img)
+      return (false);
+
+    nav_time_change_obj (img, factor_begin, factor_end);
+  }
+
+  return (true);
+} /* nav_time_change */
+
+function nav_move_earlier (img_id)
+{
+  return (nav_time_change (img_id, -0.2, -0.2));
+} /* nav_move_earlier */
+
+function nav_move_later (img_id)
+{
+  return (nav_time_change (img_id, +0.2, +0.2));
+} /* nav_move_later */
+
+function nav_zoom_in (img_id)
+{
+  return (nav_time_change (img_id, +0.2, -0.2));
+} /* nav_zoom_in */
+
+function nav_zoom_out (img_id)
+{
+  return (nav_time_change (img_id, (-1.0 / 3.0), (1.0 / 3.0)));
+} /* nav_zoom_in */
+
+function nav_set_reference (img_id)
+{
+  var img;
+  var all_images;
+  var tmp;
+  var i;
+
+  img = document.getElementById (img_id);
+  if (!img || (img.className != "graph_image")
+      || !img.navTimeBegin || !img.navTimeEnd)
+    return;
+
+  all_images = document.getElementsByTagName ("img");
+  for (i = 0; i < all_images.length; i++)
+  {
+    tmp = all_images[i];
+    if (!tmp || (tmp.className != "graph_image")
+        || !tmp.navTimeBegin || !tmp.navTimeEnd)
+      continue;
+
+    if (tmp.id == img_id)
+      continue;
+
+    tmp.navTimeBegin = img.navTimeBegin;
+    tmp.navTimeEnd = img.navTimeEnd;
+
+    nav_image_repaint (tmp);
+  }
+} /* nav_set_reference */
+
+/* 
+ * TODO: calculate the mouse position relative to the image in a cross-browser
+ * manner.
+ */
+function nav_calculate_offset_x (obj)
+{
+  var offset = 0;
+
+  if (!obj)
+    return (offset);
+
+  offset = obj.offsetLeft;
+  if (obj.offsetParent)
+    offset += nav_calculate_offset_x (obj.offsetParent);
+
+  return (offset);
+} /* nav_calculate_offset_x */
+
+function nav_calculate_event_x (e)
+{
+  var pos = 0;
+  var off = 0;
+
+  if (!e || !e.target)
+    return;
+  
+  off = nav_calculate_offset_x (e.target);
+
+  if (e.pageX || e.pageY)
+  {
+    pos = e.pageX;
+  }
+  else if (e.clientX || e.clientY)
+  {
+    pos = e.clientX + document.body.scrollLeft
+      + document.documentElement.scrollLeft;
+  }
+
+  return (pos);
+} /* nav_calculate_event_x */
+
+function nav_recenter (e)
+{
+  var x;
+  var y;
+  var img;
+  var diff;
+  var time_old_center;
+  var time_new_center;
+  var width;
+
+  img = e.target;
+  if (!img || (img.className != "graph_image")
+      || !img.navTimeBegin || !img.navTimeEnd)
+    return;
+
+  width = img.width - 97;
+
+  x = e.layerX - 70;
+  if (!x || (x < 0) || (x > width))
+    return;
+
+  y = e.layerY;
+  if (!y || (y < 35) || (y > 135))
+    return;
+
+  diff = img.navTimeEnd - img.navTimeBegin;
+
+  time_old_center = img.navTimeBegin + (diff / 2.0);
+  time_new_center = img.navTimeBegin + (x * diff / width);
+
+  img.navTimeBegin += (time_new_center - time_old_center);
+  img.navTimeEnd   += (time_new_center - time_old_center);
+} /* nav_recenter */
+
+function nav_handle_dblclick (e)
+{
+  var img;
+
+  /* M$IE */
+  if (!e)
+    e = window.event;
+
+  img = e.target;
+  if (!img || (img.className != "graph_image")
+      || !img.navTimeBegin || !img.navTimeEnd)
+    return;
+
+  nav_recenter (e);
+  nav_image_repaint (img);
+
+  // e.returnValue = false;
+} /* nav_handle_dblclick */
+
+/* Taken from <http://adomas.org/javascript-mouse-wheel/> */
+function nav_handle_wheel (e)
+{
+  var delta = 0;
+  var img;
+  
+  /* M$IE */
+  if (!e)
+    e = window.event;
+
+  img = e.target;
+  if (!img || (img.className != "graph_image")
+      || !img.navTimeBegin || !img.navTimeEnd)
+    return;
+
+  /* Opera and M$IE */
+  if (e.wheelDelta)
+  {
+    delta = e.wheelDelta; 
+    if (window.opera)
+      delta = delta * (-1);
+  }
+  else if (e.detail)
+  {
+    delta = e.detail * (-1);
+  }
+
+  if (!delta)
+    return;
+
+  nav_recenter (e);
+  if (delta > 0)
+    nav_zoom_in (img.id);
+  else
+    nav_zoom_out (img.id);
+
+  if (e.preventDefault)
+    e.preventDefault ();
+  e.returnValue = false;
+} /* function nav_handle_wheel */
+
+/* vim: set sw=2 sts=2 et : */
diff --git a/contrib/collection3/share/shortcut-icon.png b/contrib/collection3/share/shortcut-icon.png
new file mode 100644 (file)
index 0000000..6af57e5
Binary files /dev/null and b/contrib/collection3/share/shortcut-icon.png differ
diff --git a/contrib/collection3/share/style.css b/contrib/collection3/share/style.css
new file mode 100644 (file)
index 0000000..8c12951
--- /dev/null
@@ -0,0 +1,79 @@
+div.graph
+{
+}
+
+div.graph_canvas div.graph_float
+{
+  float: left;
+  position: relative;
+}
+
+div.graph_float div.controls
+{
+  display: none;
+  position: absolute;
+}
+
+div.graph_float:hover div.controls
+{
+  display: block;
+}
+
+div.graph_float div.controls.zoom
+{
+  right: 5px;
+  bottom: 10px;
+}
+
+div.graph_float div.controls.preset
+{
+  right: 5px;
+  top: 5px;
+}
+
+div.graph_float div.controls div
+{
+  display: block;
+
+  color: gray;
+  background: white;
+
+  text-decoration: none;
+  text-align: center;
+  font-size: small;
+
+  cursor: pointer;
+
+  border: 1px solid gray;
+  width: 1em;
+  height: 1em;
+  padding: 1px;
+  margin: 0px;
+}
+
+div.graph_float div.controls div:hover
+{
+  color: black;
+  border-color: black;
+}
+
+div.graph_float div.controls.preset div
+{
+  margin: 1px 0px 1px 0px;
+}
+
+div.graph_float div.controls.zoom div
+{
+  float: left;
+  margin: 0px 1px 0px 1px;
+}
+
+table
+{
+  border-collapse: collapse;
+}
+td, th
+{
+  border: 1px solid gray;
+}
+/* vim: set sw=2 sts=2 et : */
index f25c155..f95d54d 100755 (executable)
@@ -1,7 +1,7 @@
 #!/usr/bin/perl
 #
 # collectd - contrib/cussh.pl
-# Copyright (C) 2007  Sebastian Harl
+# Copyright (C) 2007-2008  Sebastian Harl
 #
 # 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
@@ -56,12 +56,20 @@ use Collectd::Unixsock();
        my $path = $ARGV[0] || "/var/run/collectd-unixsock";
        my $sock = Collectd::Unixsock->new($path);
 
+       my $cmds = {
+               HELP    => \&cmd_help,
+               PUTVAL  => \&putval,
+               GETVAL  => \&getval,
+               FLUSH   => \&flush,
+               LISTVAL => \&listval,
+       };
+
        if (! $sock) {
                print STDERR "Unable to connect to $path!\n";
                exit 1;
        }
 
-       print "cussh version 0.1, Copyright (C) 2007 Sebastian Harl\n"
+       print "cussh version 0.2, Copyright (C) 2007-2008 Sebastian Harl\n"
                . "cussh comes with ABSOLUTELY NO WARRANTY. This is free software,\n"
                . "and you are welcome to redistribute it under certain conditions.\n"
                . "See the GNU General Public License 2 for more details.\n\n";
@@ -70,20 +78,21 @@ use Collectd::Unixsock();
                print "cussh> ";
                my $line = <STDIN>;
 
-               last if ((! $line) || ($line =~ m/^quit$/i));
+               last if (! $line);
+
+               chomp $line;
 
-               my ($cmd) = $line =~ m/^(\w+)\s+/;
+               last if ($line =~ m/^quit$/i);
+
+               my ($cmd) = $line =~ m/^(\w+)\s*/;
                $line = $';
 
                next if (! $cmd);
                $cmd = uc $cmd;
 
                my $f = undef;
-               if ($cmd eq "PUTVAL") {
-                       $f = \&putval;
-               }
-               elsif ($cmd eq "GETVAL") {
-                       $f = \&getval;
+               if (defined $cmds->{$cmd}) {
+                       $f = $cmds->{$cmd};
                }
                else {
                        print STDERR "ERROR: Unknown command $cmd!\n";
@@ -119,16 +128,49 @@ sub getid {
        return \%id;
 }
 
+sub putid {
+       my $ident = shift || return;
+
+       my $string;
+
+       $string = $ident->{'host'} . "/" . $ident->{'plugin'};
+
+       if (defined $ident->{'plugin_instance'}) {
+               $string .= "-" . $ident->{'plugin_instance'};
+       }
+
+       $string .= "/" . $ident->{'type'};
+
+       if (defined $ident->{'type_instance'}) {
+               $string .= "-" . $ident->{'type_instance'};
+       }
+       return $string;
+}
+
 =head1 COMMANDS
 
 =over 4
 
-=item B<GETVAL> I<Identifier>
+=item B<HELP>
 
-=item B<PUTVAL> I<Identifier> I<Valuelist>
+=cut
 
-These commands follow the exact same syntax as described in
-L<collectd-unixsock(5)>.
+sub cmd_help {
+       print <<HELP;
+Available commands:
+  HELP
+  PUTVAL
+  GETVAL
+  FLUSH
+  LISTVAL
+
+See the embedded Perldoc documentation for details. To do that, run:
+  perldoc $0
+HELP
+       return 1;
+} # cmd_help
+
+=item B<GETVAL> I<Identifier>
 
 =cut
 
@@ -138,23 +180,36 @@ sub putval {
 
        my $id = getid(\$line);
 
-       return if (! $id);
+       if (! $id) {
+               print STDERR $sock->{'error'} . $/;
+               return;
+       }
 
        my ($time, @values) = split m/:/, $line;
-       return $sock->putval(%$id, $time, \@values);
+       return $sock->putval(%$id, time => $time, values => \@values);
 }
 
+=item B<PUTVAL> I<Identifier> I<Valuelist>
+
+=cut
+
 sub getval {
        my $sock = shift || return;
        my $line = shift || return;
 
        my $id = getid(\$line);
 
-       return if (! $id);
+       if (! $id) {
+               print STDERR $sock->{'error'} . $/;
+               return;
+       }
 
        my $vals = $sock->getval(%$id);
 
-       return if (! $vals);
+       if (! $vals) {
+               print STDERR $sock->{'error'} . $/;
+               return;
+       }
 
        foreach my $key (keys %$vals) {
                print "\t$key: $vals->{$key}\n";
@@ -162,6 +217,75 @@ sub getval {
        return 1;
 }
 
+=item B<FLUSH> [B<timeout>=I<$timeout>] [B<plugin>=I<$plugin>[ ...]]
+
+=cut
+
+sub flush {
+       my $sock = shift || return;
+       my $line = shift;
+
+       my $res;
+
+       if (! $line) {
+               $res = $sock->flush();
+       }
+       else {
+               my %args = ();
+
+               foreach my $i (split m/ /, $line) {
+                       my ($option, $value) = $i =~ m/^([^=]+)=(.+)$/;
+                       next if (! ($option && $value));
+
+                       if ($option eq "plugin") {
+                               push @{$args{"plugins"}}, $value;
+                       }
+                       elsif ($option eq "timeout") {
+                               $args{"timeout"} = $value;
+                       }
+                       else {
+                               print STDERR "Invalid option \"$option\".\n";
+                               return;
+                       }
+               }
+
+               $res = $sock->flush(%args);
+       }
+
+       if (! $res) {
+               print STDERR $sock->{'error'} . $/;
+               return;
+       }
+       return 1;
+}
+
+=item B<LISTVAL>
+
+=cut
+
+sub listval {
+       my $sock = shift || return;
+
+       my @res;
+
+       @res = $sock->listval();
+
+       if (! @res) {
+               print STDERR $sock->{'error'} . $/;
+               return;
+       }
+
+       foreach my $ident (@res) {
+               print $ident->{'time'} . " " . putid($ident) . $/;
+       }
+       return 1;
+}
+
+=back
+
+These commands follow the exact same syntax as described in
+L<collectd-unixsock(5)>.
+
 =head1 SEE ALSO
 
 L<collectd(1)>, L<collectd-unisock(5)>
index ed41827..ed19a7b 100755 (executable)
@@ -170,22 +170,23 @@ for (@Files)
                        print "./rrd_filter.px -i '$InDir/$orig_filename' -m '${src_ds}:${dst_ds}' -o '$OutDir/$dest_filename'\n";
                }
        }
-       elsif (exists ($TypeRename{$orig->{'type'}}))
+       else
+       {
+               print "cp '$InDir/$orig_filename' '$OutDir/$dest_filename'\n";
+       }
+
+       if (exists ($TypeRename{$orig->{'type'}}))
        {
                my $src_dses = $TypeRename{$orig->{'type'}}->{'from'};
                my $dst_dses = $TypeRename{$orig->{'type'}}->{'to'};
-               my @sed_prog = ();
 
+               print "rrdtool tune '$OutDir/$dest_filename'";
                for (my $i = 0; $i < @$src_dses; $i++)
                {
-                       push (@sed_prog, 's/^' . $src_dses->[$i] . '$/' . $dst_dses->[$i] . '/g;');
+                       print " --data-source-rename "
+                               . $src_dses->[$i] . ':' . $dst_dses->[$i];
                }
-
-               print "rrdtool dump '$InDir/$orig_filename' | sed -e '" . join (' ', @sed_prog) . "' | rrdtool restore - '$OutDir/$dest_filename'\n";
-       }
-       else
-       {
-               print "cp '$InDir/$orig_filename' '$OutDir/$dest_filename'\n";
+               print "\n";
        }
 }
 
index 7dad340..67a79b8 100644 (file)
@@ -1,4 +1,7 @@
 SUBDIRS =
+if BUILD_WITH_OWN_LIBIPTC
+SUBDIRS += libiptc
+endif
 if BUILD_WITH_OWN_LIBOCONFIG
 SUBDIRS += liboconfig
 endif
@@ -30,7 +33,10 @@ collectd_SOURCES = collectd.c collectd.h \
                   utils_cache.c utils_cache.h \
                   utils_ignorelist.c utils_ignorelist.h \
                   utils_llist.c utils_llist.h \
+                  utils_tail_match.c utils_tail_match.h \
+                  utils_match.c utils_match.h \
                   utils_mount.c utils_mount.h \
+                  utils_tail.c utils_tail.h \
                   utils_threshold.c utils_threshold.h \
                   types_list.c types_list.h
 
@@ -118,6 +124,16 @@ collectd_LDADD += "-dlopen" apple_sensors.la
 collectd_DEPENDENCIES += apple_sensors.la
 endif
 
+if BUILD_PLUGIN_ASCENT
+pkglib_LTLIBRARIES += ascent.la
+ascent_la_SOURCES = ascent.c
+ascent_la_LDFLAGS = -module -avoid-version
+ascent_la_CFLAGS = $(BUILD_WITH_LIBCURL_CFLAGS) $(BUILD_WITH_LIBXML2_CFLAGS)
+ascent_la_LIBADD = $(BUILD_WITH_LIBCURL_LIBS) $(BUILD_WITH_LIBXML2_LIBS)
+collectd_LDADD += "-dlopen" apache.la
+collectd_DEPENDENCIES += ascent.la
+endif
+
 if BUILD_PLUGIN_BATTERY
 pkglib_LTLIBRARIES += battery.la
 battery_la_SOURCES = battery.c
@@ -132,13 +148,19 @@ endif
 if BUILD_PLUGIN_CPU
 pkglib_LTLIBRARIES += cpu.la
 cpu_la_SOURCES = cpu.c
+cpu_la_CFLAGS =
 cpu_la_LDFLAGS = -module -avoid-version
+cpu_la_LIBADD = 
 if BUILD_WITH_LIBKSTAT
 cpu_la_LDFLAGS += -lkstat
 endif
 if BUILD_WITH_LIBDEVINFO
 cpu_la_LDFLAGS += -ldevinfo
 endif
+if BUILD_WITH_LIBSTATGRAB
+cpu_la_CFLAGS += $(BUILD_WITH_LIBSTATGRAB_CFLAGS)
+cpu_la_LIBADD += $(BUILD_WITH_LIBSTATGRAB_LDFLAGS)
+endif
 collectd_LDADD += "-dlopen" cpu.la
 collectd_DEPENDENCIES += cpu.la
 endif
@@ -170,7 +192,9 @@ endif
 if BUILD_PLUGIN_DISK
 pkglib_LTLIBRARIES += disk.la
 disk_la_SOURCES = disk.c
+disk_la_CFLAGS =
 disk_la_LDFLAGS = -module -avoid-version
+disk_la_LIBADD = 
 if BUILD_WITH_LIBKSTAT
 disk_la_LDFLAGS += -lkstat
 endif
@@ -180,6 +204,10 @@ endif
 if BUILD_WITH_LIBIOKIT
 disk_la_LDFLAGS += -lIOKit
 endif
+if BUILD_WITH_LIBSTATGRAB
+disk_la_CFLAGS += $(BUILD_WITH_LIBSTATGRAB_CFLAGS)  
+disk_la_LIBADD += $(BUILD_WITH_LIBSTATGRAB_LDFLAGS)
+endif
 collectd_LDADD += "-dlopen" disk.la
 collectd_DEPENDENCIES += disk.la
 endif
@@ -259,14 +287,31 @@ endif # BUILD_PLUGIN_INTERFACE
 if BUILD_PLUGIN_IPTABLES
 pkglib_LTLIBRARIES += iptables.la
 iptables_la_SOURCES = iptables.c
-iptables_la_LDFLAGS = -module -avoid-version -liptc
+iptables_la_LDFLAGS = -module -avoid-version
+if BUILD_WITH_OWN_LIBIPTC
+iptables_la_LIBADD  = libiptc/libiptc.la
+iptables_la_DEPENDENCIES = libiptc/libiptc.la
+else
+iptables_la_LDFLAGS += -liptc
+endif
 collectd_LDADD += "-dlopen" iptables.la
 collectd_DEPENDENCIES += iptables.la
 endif
 
+if BUILD_PLUGIN_IPMI
+pkglib_LTLIBRARIES += ipmi.la
+ipmi_la_SOURCES = ipmi.c
+ipmi_la_CFLAGS = $(BUILD_WITH_OPENIPMI_CFLAGS)
+ipmi_la_LDFLAGS = -module -avoid-version
+ipmi_la_LIBADD = $(BUILD_WITH_OPENIPMI_LIBS)
+collectd_LDADD += "-dlopen" ipmi.la
+collectd_DEPENDENCIES += ipmi.la
+endif
+
 if BUILD_PLUGIN_IPVS
 pkglib_LTLIBRARIES += ipvs.la
 ipvs_la_SOURCES = ipvs.c
+ipvs_la_CFLAGS = $(KERNEL_CFLAGS)
 ipvs_la_LDFLAGS = -module -avoid-version
 collectd_LDADD += "-dlopen" ipvs.la
 collectd_DEPENDENCIES += ipvs.la
@@ -470,6 +515,14 @@ collectd_LDADD += "-dlopen" ping.la
 collectd_DEPENDENCIES += ping.la
 endif
 
+if BUILD_PLUGIN_POWERDNS
+pkglib_LTLIBRARIES += powerdns.la
+powerdns_la_SOURCES = powerdns.c
+powerdns_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" powerdns.la
+collectd_DEPENDENCIES += powerdns.la
+endif
+
 if BUILD_PLUGIN_PROCESSES
 pkglib_LTLIBRARIES += processes.la
 processes_la_SOURCES = processes.c
@@ -554,6 +607,14 @@ collectd_LDADD += "-dlopen" syslog.la
 collectd_DEPENDENCIES += syslog.la
 endif
 
+if BUILD_PLUGIN_TAIL
+pkglib_LTLIBRARIES += tail.la
+tail_la_SOURCES = tail.c
+tail_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" tail.la
+collectd_DEPENDENCIES += tail.la
+endif
+
 if BUILD_PLUGIN_TAPE
 pkglib_LTLIBRARIES += tape.la
 tape_la_SOURCES = tape.c
@@ -570,9 +631,22 @@ collectd_LDADD += "-dlopen" tcpconns.la
 collectd_DEPENDENCIES += tcpconns.la
 endif
 
+if BUILD_PLUGIN_TEAMSPEAK2
+pkglib_LTLIBRARIES += teamspeak2.la
+teamspeak2_la_SOURCES = teamspeak2.c
+teamspeak2_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" teamspeak2.la
+collectd_DEPENDENCIES += teamspeak2.la
+endif
+
 if BUILD_PLUGIN_UNIXSOCK
 pkglib_LTLIBRARIES += unixsock.la
-unixsock_la_SOURCES = unixsock.c utils_cmd_putval.h utils_cmd_putval.c utils_cmd_putnotif.h utils_cmd_putnotif.c
+unixsock_la_SOURCES = unixsock.c \
+                     utils_cmd_flush.h utils_cmd_flush.c \
+                     utils_cmd_getval.h utils_cmd_getval.c \
+                     utils_cmd_listval.h utils_cmd_listval.c \
+                     utils_cmd_putval.h utils_cmd_putval.c \
+                     utils_cmd_putnotif.h utils_cmd_putnotif.c
 unixsock_la_LDFLAGS = -module -avoid-version -lpthread
 collectd_LDADD += "-dlopen" unixsock.la
 collectd_DEPENDENCIES += unixsock.la
@@ -581,7 +655,13 @@ endif
 if BUILD_PLUGIN_USERS
 pkglib_LTLIBRARIES += users.la
 users_la_SOURCES = users.c
+users_la_CFLAGS =
 users_la_LDFLAGS = -module -avoid-version
+users_la_LIBADD =
+if BUILD_WITH_LIBSTATGRAB
+users_la_CFLAGS += $(BUILD_WITH_LIBSTATGRAB_CFLAGS)
+users_la_LIBADD += $(BUILD_WITH_LIBSTATGRAB_LDFLAGS)
+endif
 collectd_LDADD += "-dlopen" users.la
 collectd_DEPENDENCIES += users.la
 endif
@@ -596,6 +676,14 @@ collectd_LDADD += "-dlopen" uuid.la
 collectd_DEPENDENCIES += uuid.la
 endif
 
+if BUILD_PLUGIN_VMEM
+pkglib_LTLIBRARIES += vmem.la
+vmem_la_SOURCES = vmem.c
+vmem_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" vmem.la
+collectd_DEPENDENCIES += vmem.la
+endif
+
 if BUILD_PLUGIN_VSERVER
 pkglib_LTLIBRARIES += vserver.la
 vserver_la_SOURCES = vserver.c
index 54a5d56..a2f41f4 100644 (file)
 
 #include <curl/curl.h>
 
-static char *url    = NULL;
-static char *user   = NULL;
-static char *pass   = NULL;
-static char *cacert = NULL;
+static char *url         = NULL;
+static char *user        = NULL;
+static char *pass        = NULL;
+static char *verify_peer = NULL;
+static char *verify_host = NULL;
+static char *cacert      = NULL;
 
 static CURL *curl = NULL;
 
@@ -46,6 +48,8 @@ static const char *config_keys[] =
        "URL",
        "User",
        "Password",
+       "VerifyPeer",
+       "VerifyHost",
        "CACert"
 };
 static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
@@ -102,6 +106,10 @@ static int config (const char *key, const char *value)
                return (config_set (&user, value));
        else if (strcasecmp (key, "password") == 0)
                return (config_set (&pass, value));
+       else if (strcasecmp (key, "verifypeer") == 0)
+               return (config_set (&verify_peer, value));
+       else if (strcasecmp (key, "verifyhost") == 0)
+               return (config_set (&verify_host, value));
        else if (strcasecmp (key, "cacert") == 0)
                return (config_set (&cacert, value));
        else
@@ -154,6 +162,24 @@ static int init (void)
 
        curl_easy_setopt (curl, CURLOPT_URL, url);
 
+       if ((verify_peer == NULL) || (strcmp (verify_peer, "true") == 0))
+       {
+               curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, 1);
+       }
+       else
+       {
+               curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, 0);
+       }
+
+       if ((verify_host == NULL) || (strcmp (verify_host, "true") == 0))
+       {
+               curl_easy_setopt (curl, CURLOPT_SSL_VERIFYHOST, 2);
+       }
+       else
+       {
+               curl_easy_setopt (curl, CURLOPT_SSL_VERIFYHOST, 0);
+       }
+
        if (cacert != NULL)
        {
                curl_easy_setopt (curl, CURLOPT_CAINFO, cacert);
diff --git a/src/ascent.c b/src/ascent.c
new file mode 100644 (file)
index 0000000..8c0bbec
--- /dev/null
@@ -0,0 +1,596 @@
+/**
+ * collectd - src/ascent.c
+ * Copyright (C) 2008  Florian octo Forster
+ *
+ * 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; only version 2 of the License is applicable.
+ *
+ * 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 St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+
+#include <curl/curl.h>
+#include <libxml/parser.h>
+
+static char *races_list[] = /* {{{ */
+{
+  NULL,
+  "Human",    /*  1 */
+  "Orc",      /*  2 */
+  "Dwarf",    /*  3 */
+  "Nightelf", /*  4 */
+  "Undead",   /*  5 */
+  "Tauren",   /*  6 */
+  "Gnome",    /*  7 */
+  "Troll",    /*  8 */
+  NULL,
+  "Bloodelf", /* 10 */
+  "Draenei"   /* 11 */
+}; /* }}} */
+#define RACES_LIST_LENGTH STATIC_ARRAY_SIZE (races_list)
+
+static char *classes_list[] = /* {{{ */
+{
+  NULL,
+  "Warrior", /*  1 */
+  "Paladin", /*  2 */
+  "Hunter",  /*  3 */
+  "Rogue",   /*  4 */
+  "Priest",  /*  5 */
+  NULL,
+  "Shaman",  /*  7 */
+  "Mage",    /*  8 */
+  "Warlock", /*  9 */
+  NULL,
+  "Druid"    /* 11 */
+}; /* }}} */
+#define CLASSES_LIST_LENGTH STATIC_ARRAY_SIZE (classes_list)
+
+static char *genders_list[] = /* {{{ */
+{
+  "Male",
+  "Female"
+}; /* }}} */
+#define GENDERS_LIST_LENGTH STATIC_ARRAY_SIZE (genders_list)
+
+struct player_stats_s
+{
+  int races[RACES_LIST_LENGTH];
+  int classes[CLASSES_LIST_LENGTH];
+  int genders[GENDERS_LIST_LENGTH];
+  int level_sum;
+  int level_num;
+  int latency_sum;
+  int latency_num;
+};
+typedef struct player_stats_s player_stats_t;
+
+struct player_info_s
+{
+  int race;
+  int class;
+  int gender;
+  int level;
+  int latency;
+};
+typedef struct player_info_s player_info_t;
+#define PLAYER_INFO_STATIC_INIT { -1, -1, -1, -1, -1 }
+
+static char *url    = NULL;
+static char *user   = NULL;
+static char *pass   = NULL;
+static char *cacert = NULL;
+
+static CURL *curl = NULL;
+
+static char  *ascent_buffer = NULL;
+static size_t ascent_buffer_size = 0;
+static size_t ascent_buffer_fill = 0;
+static char   ascent_curl_error[CURL_ERROR_SIZE];
+
+static const char *config_keys[] =
+{
+  "URL",
+  "User",
+  "Password",
+  "CACert"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+
+static int ascent_submit_gauge (const char *plugin_instance, /* {{{ */
+    const char *type, const char *type_instance, gauge_t value)
+{
+  value_t values[1];
+  value_list_t vl = VALUE_LIST_INIT;
+
+  values[0].gauge = value;
+
+  vl.values = values;
+  vl.values_len = 1;
+  vl.time = time (NULL);
+  sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+  sstrncpy (vl.plugin, "ascent", sizeof (vl.plugin));
+
+  if (plugin_instance != NULL)
+    sstrncpy (vl.plugin_instance, plugin_instance,
+        sizeof (vl.plugin_instance));
+
+  if (type_instance != NULL)
+    sstrncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
+
+  plugin_dispatch_values (type, &vl);
+  return (0);
+} /* }}} int ascent_submit_gauge */
+
+static size_t ascent_curl_callback (void *buf, size_t size, size_t nmemb, /* {{{ */
+    void *stream)
+{
+  size_t len = size * nmemb;
+
+  if (len <= 0)
+    return (len);
+
+  if ((ascent_buffer_fill + len) >= ascent_buffer_size)
+  {
+    char *temp;
+
+    temp = (char *) realloc (ascent_buffer,
+        ascent_buffer_fill + len + 1);
+    if (temp == NULL)
+    {
+      ERROR ("ascent plugin: realloc failed.");
+      return (0);
+    }
+    ascent_buffer = temp;
+    ascent_buffer_size = ascent_buffer_fill + len + 1;
+  }
+
+  memcpy (ascent_buffer + ascent_buffer_fill, (char *) buf, len);
+  ascent_buffer_fill += len;
+  ascent_buffer[ascent_buffer_fill] = 0;
+
+  return (len);
+} /* }}} size_t ascent_curl_callback */
+
+static int ascent_submit_players (player_stats_t *ps) /* {{{ */
+{
+  int i;
+  gauge_t value;
+
+  for (i = 0; i < RACES_LIST_LENGTH; i++)
+    if (races_list[i] != NULL)
+      ascent_submit_gauge ("by-race", "players", races_list[i],
+          (gauge_t) ps->races[i]);
+
+  for (i = 0; i < CLASSES_LIST_LENGTH; i++)
+    if (classes_list[i] != NULL)
+      ascent_submit_gauge ("by-class", "players", classes_list[i],
+          (gauge_t) ps->classes[i]);
+
+  for (i = 0; i < GENDERS_LIST_LENGTH; i++)
+    if (genders_list[i] != NULL)
+      ascent_submit_gauge ("by-gender", "players", genders_list[i],
+          (gauge_t) ps->genders[i]);
+
+  if (ps->level_num <= 0)
+    value = NAN;
+  else
+    value = ((double) ps->level_sum) / ((double) ps->level_num);
+  ascent_submit_gauge (NULL, "gauge", "avg-level", value);
+
+  /* Latency is in ms, but we store seconds. */
+  if (ps->latency_num <= 0)
+    value = NAN;
+  else
+    value = ((double) ps->latency_sum) / (1000.0 * ((double) ps->latency_num));
+  ascent_submit_gauge (NULL, "latency", "average", value);
+
+  return (0);
+} /* }}} int ascent_submit_players */
+
+static int ascent_account_player (player_stats_t *ps, /* {{{ */
+    player_info_t *pi)
+{
+  if (pi->race >= 0)
+  {
+    if ((pi->race >= RACES_LIST_LENGTH)
+        || (races_list[pi->race] == NULL))
+      ERROR ("ascent plugin: Ignoring invalid numeric race %i.", pi->race);
+    else
+      ps->races[pi->race]++;
+  }
+
+  if (pi->class >= 0)
+  {
+    if ((pi->class >= CLASSES_LIST_LENGTH)
+        || (classes_list[pi->class] == NULL))
+      ERROR ("ascent plugin: Ignoring invalid numeric class %i.", pi->class);
+    else
+      ps->classes[pi->class]++;
+  }
+
+  if (pi->gender >= 0)
+  {
+    if ((pi->gender >= GENDERS_LIST_LENGTH)
+        || (genders_list[pi->gender] == NULL))
+      ERROR ("ascent plugin: Ignoring invalid numeric gender %i.",
+          pi->gender);
+    else
+      ps->genders[pi->gender]++;
+  }
+
+
+  if (pi->level > 0)
+  {
+    ps->level_sum += pi->level;
+    ps->level_num++;
+  }
+
+  if (pi->latency >= 0)
+  {
+    ps->latency_sum += pi->latency;
+    ps->latency_num++;
+  }
+
+  return (0);
+} /* }}} int ascent_account_player */
+
+static int ascent_xml_submit_gauge (xmlDoc *doc, xmlNode *node, /* {{{ */
+    const char *plugin_instance, const char *type, const char *type_instance)
+{
+  char *str_ptr;
+  gauge_t value;
+
+  str_ptr = (char *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
+  if (str_ptr == NULL)
+  {
+    ERROR ("ascent plugin: ascent_xml_submit_gauge: xmlNodeListGetString failed.");
+    return (-1);
+  }
+
+  if (strcasecmp ("N/A", str_ptr) == 0)
+    value = NAN;
+  else
+  {
+    char *end_ptr = NULL;
+    value = strtod (str_ptr, &end_ptr);
+    if (str_ptr == end_ptr)
+    {
+      ERROR ("ascent plugin: ascent_xml_submit_gauge: strtod failed.");
+      return (-1);
+    }
+  }
+
+  return (ascent_submit_gauge (plugin_instance, type, type_instance, value));
+} /* }}} int ascent_xml_submit_gauge */
+
+static int ascent_xml_read_int (xmlDoc *doc, xmlNode *node, /* {{{ */
+    int *ret_value)
+{
+  char *str_ptr;
+  int value;
+
+  str_ptr = (char *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
+  if (str_ptr == NULL)
+  {
+    ERROR ("ascent plugin: ascent_xml_read_int: xmlNodeListGetString failed.");
+    return (-1);
+  }
+
+  if (strcasecmp ("N/A", str_ptr) == 0)
+    value = -1;
+  else
+  {
+    char *end_ptr = NULL;
+    value = strtol (str_ptr, &end_ptr, 0);
+    if (str_ptr == end_ptr)
+    {
+      ERROR ("ascent plugin: ascent_xml_read_int: strtol failed.");
+      return (-1);
+    }
+  }
+
+  *ret_value = value;
+  return (0);
+} /* }}} int ascent_xml_read_int */
+
+static int ascent_xml_sessions_plr (xmlDoc *doc, xmlNode *node, /* {{{ */
+    player_info_t *pi)
+{
+  xmlNode *child;
+
+  for (child = node->xmlChildrenNode; child != NULL; child = child->next)
+  {
+    if ((xmlStrcmp ((const xmlChar *) "comment", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "text", child->name) == 0))
+      /* ignore */;
+    else if (xmlStrcmp ((const xmlChar *) "race", child->name) == 0)
+      ascent_xml_read_int (doc, child, &pi->race);
+    else if (xmlStrcmp ((const xmlChar *) "class", child->name) == 0)
+      ascent_xml_read_int (doc, child, &pi->class);
+    else if (xmlStrcmp ((const xmlChar *) "gender", child->name) == 0)
+      ascent_xml_read_int (doc, child, &pi->gender);
+    else if (xmlStrcmp ((const xmlChar *) "level", child->name) == 0)
+      ascent_xml_read_int (doc, child, &pi->level);
+    else if (xmlStrcmp ((const xmlChar *) "latency", child->name) == 0)
+      ascent_xml_read_int (doc, child, &pi->latency);
+    else if ((xmlStrcmp ((const xmlChar *) "name", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "pvprank", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "map", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "areaid", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "xpos", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "ypos", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "onime", child->name) == 0))
+      /* ignore */;
+    else
+    {
+      WARNING ("ascent plugin: ascent_xml_status: Unknown tag: %s", child->name);
+    }
+  } /* for (child) */
+
+  return (0);
+} /* }}} int ascent_xml_sessions_plr */
+
+static int ascent_xml_sessions (xmlDoc *doc, xmlNode *node) /* {{{ */
+{
+  xmlNode *child;
+  player_stats_t ps;
+
+  memset (&ps, 0, sizeof (ps));
+
+  for (child = node->xmlChildrenNode; child != NULL; child = child->next)
+  {
+    if ((xmlStrcmp ((const xmlChar *) "comment", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "text", child->name) == 0))
+      /* ignore */;
+    else if (xmlStrcmp ((const xmlChar *) "plr", child->name) == 0)
+    {
+      int status;
+      player_info_t pi = PLAYER_INFO_STATIC_INIT;
+
+      status = ascent_xml_sessions_plr (doc, child, &pi);
+      if (status == 0)
+        ascent_account_player (&ps, &pi);
+    }
+    else
+    {
+      WARNING ("ascent plugin: ascent_xml_status: Unknown tag: %s", child->name);
+    }
+  } /* for (child) */
+
+  ascent_submit_players (&ps);
+
+  return (0);
+} /* }}} int ascent_xml_sessions */
+
+static int ascent_xml_status (xmlDoc *doc, xmlNode *node) /* {{{ */
+{
+  xmlNode *child;
+
+  for (child = node->xmlChildrenNode; child != NULL; child = child->next)
+  {
+    if ((xmlStrcmp ((const xmlChar *) "comment", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "text", child->name) == 0))
+      /* ignore */;
+    else if (xmlStrcmp ((const xmlChar *) "alliance", child->name) == 0)
+      ascent_xml_submit_gauge (doc, child, NULL, "players", "alliance");
+    else if (xmlStrcmp ((const xmlChar *) "horde", child->name) == 0)
+      ascent_xml_submit_gauge (doc, child, NULL, "players", "horde");
+    else if (xmlStrcmp ((const xmlChar *) "qplayers", child->name) == 0)
+      ascent_xml_submit_gauge (doc, child, NULL, "players", "queued");
+    else if ((xmlStrcmp ((const xmlChar *) "acceptedconns", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "avglat", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "cdbquerysize", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "cpu", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "fthreads", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "gmcount", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "lastupdate", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "ontime", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "oplayers", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "peakcount", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "platform", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "ram", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "threads", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "uptime", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "wdbquerysize", child->name) == 0))
+      /* ignore */;
+    else
+    {
+      WARNING ("ascent plugin: ascent_xml_status: Unknown tag: %s", child->name);
+    }
+  } /* for (child) */
+
+  return (0);
+} /* }}} int ascent_xml_status */
+
+static int ascent_xml (const char *data) /* {{{ */
+{
+  xmlDoc *doc;
+  xmlNode *cur;
+  xmlNode *child;
+
+#if 0
+  doc = xmlParseMemory (data, strlen (data),
+      /* URL = */ "ascent.xml",
+      /* encoding = */ NULL,
+      /* options = */ 0);
+#else
+  doc = xmlParseMemory (data, strlen (data));
+#endif
+  if (doc == NULL)
+  {
+    ERROR ("ascent plugin: xmlParseMemory failed.");
+    return (-1);
+  }
+
+  cur = xmlDocGetRootElement (doc);
+  if (cur == NULL)
+  {
+    ERROR ("ascent plugin: XML document is empty.");
+    xmlFreeDoc (doc);
+    return (-1);
+  }
+
+  if (xmlStrcmp ((const xmlChar *) "serverpage", cur->name) != 0)
+  {
+    ERROR ("ascent plugin: XML root element is not \"serverpage\".");
+    xmlFreeDoc (doc);
+    return (-1);
+  }
+
+  for (child = cur->xmlChildrenNode; child != NULL; child = child->next)
+  {
+    if ((xmlStrcmp ((const xmlChar *) "comment", child->name) == 0)
+        || (xmlStrcmp ((const xmlChar *) "text", child->name) == 0))
+      /* ignore */;
+    else if (xmlStrcmp ((const xmlChar *) "status", child->name) == 0)
+      ascent_xml_status (doc, child);
+    else if (xmlStrcmp ((const xmlChar *) "instances", child->name) == 0)
+      /* ignore for now */;
+    else if (xmlStrcmp ((const xmlChar *) "gms", child->name) == 0)
+      /* ignore for now */;
+    else if (xmlStrcmp ((const xmlChar *) "sessions", child->name) == 0)
+      ascent_xml_sessions (doc, child);
+    else
+    {
+      WARNING ("ascent plugin: ascent_xml: Unknown tag: %s", child->name);
+    }
+  } /* for (child) */
+
+  xmlFreeDoc (doc);
+  return (0);
+} /* }}} int ascent_xml */
+
+static int config_set (char **var, const char *value) /* {{{ */
+{
+  if (*var != NULL)
+  {
+    free (*var);
+    *var = NULL;
+  }
+
+  if ((*var = strdup (value)) == NULL)
+    return (1);
+  else
+    return (0);
+} /* }}} int config_set */
+
+static int ascent_config (const char *key, const char *value) /* {{{ */
+{
+  if (strcasecmp (key, "URL") == 0)
+    return (config_set (&url, value));
+  else if (strcasecmp (key, "User") == 0)
+    return (config_set (&user, value));
+  else if (strcasecmp (key, "Password") == 0)
+    return (config_set (&pass, value));
+  else if (strcasecmp (key, "CACert") == 0)
+    return (config_set (&cacert, value));
+  else
+    return (-1);
+} /* }}} int ascent_config */
+
+static int ascent_init (void) /* {{{ */
+{
+  static char credentials[1024];
+
+  if (url == NULL)
+  {
+    WARNING ("ascent plugin: ascent_init: No URL configured, "
+        "returning an error.");
+    return (-1);
+  }
+
+  if (curl != NULL)
+  {
+    curl_easy_cleanup (curl);
+  }
+
+  if ((curl = curl_easy_init ()) == NULL)
+  {
+    ERROR ("ascent plugin: ascent_init: curl_easy_init failed.");
+    return (-1);
+  }
+
+  curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, ascent_curl_callback);
+  curl_easy_setopt (curl, CURLOPT_USERAGENT, PACKAGE_NAME"/"PACKAGE_VERSION);
+  curl_easy_setopt (curl, CURLOPT_ERRORBUFFER, ascent_curl_error);
+
+  if (user != NULL)
+  {
+    int status;
+
+    status = snprintf (credentials, sizeof (credentials), "%s:%s",
+        user, (pass == NULL) ? "" : pass);
+    if (status >= sizeof (credentials))
+    {
+      ERROR ("ascent plugin: ascent_init: Returning an error because the "
+          "credentials have been truncated.");
+      return (-1);
+    }
+    credentials[sizeof (credentials) - 1] = '\0';
+
+    curl_easy_setopt (curl, CURLOPT_USERPWD, credentials);
+  }
+
+  curl_easy_setopt (curl, CURLOPT_URL, url);
+
+  if (cacert != NULL)
+    curl_easy_setopt (curl, CURLOPT_CAINFO, cacert);
+
+  return (0);
+} /* }}} int ascent_init */
+
+static int ascent_read (void) /* {{{ */
+{
+  int status;
+
+  if (curl == NULL)
+  {
+    ERROR ("ascent plugin: I don't have a CURL object.");
+    return (-1);
+  }
+
+  if (url == NULL)
+  {
+    ERROR ("ascent plugin: No URL has been configured.");
+    return (-1);
+  }
+
+  ascent_buffer_fill = 0;
+  if (curl_easy_perform (curl) != 0)
+  {
+    ERROR ("ascent plugin: curl_easy_perform failed: %s",
+        ascent_curl_error);
+    return (-1);
+  }
+
+  status = ascent_xml (ascent_buffer);
+  if (status != 0)
+    return (-1);
+  else
+    return (0);
+} /* }}} int ascent_read */
+
+void module_register (void)
+{
+  plugin_register_config ("ascent", ascent_config, config_keys, config_keys_num);
+  plugin_register_init ("ascent", ascent_init);
+  plugin_register_read ("ascent", ascent_read);
+} /* void module_register */
+
+/* vim: set sw=2 sts=2 ts=8 et fdm=marker : */
index f69a32e..59ce399 100644 (file)
@@ -171,11 +171,13 @@ table. All the options are optional, but B<plugin_instance> without B<plugin>
 or B<type_instance> without B<type> doesn't make much sense and should be
 avoided.
 
-Please note that this is the same format as used in the B<unixsock plugin>, see
-L<collectd-unixsock(5)>.
+=back
 
 =back
 
+Please note that this is the same format as used in the B<unixsock plugin>, see
+L<collectd-unixsock(5)>.
+
 When collectd exits it sends a B<SIGTERM> to all still running
 child-processes upon which they have to quit.
 
index 63effd5..1bf5b3f 100644 (file)
@@ -142,7 +142,7 @@ static int get_values (int *ret_values_num, double **ret_values,
        struct sockaddr_un sa;
        int status;
        int fd;
-       FILE *fh;
+       FILE *fh_in, *fh_out;
        char buffer[4096];
 
        int values_num;
@@ -172,8 +172,8 @@ static int get_values (int *ret_values_num, double **ret_values,
                return (-1);
        }
 
-       fh = fdopen (fd, "r+");
-       if (fh == NULL)
+       fh_in = fdopen (fd, "r");
+       if (fh_in == NULL)
        {
                fprintf (stderr, "fdopen failed: %s\n",
                                strerror (errno));
@@ -181,21 +181,37 @@ static int get_values (int *ret_values_num, double **ret_values,
                return (-1);
        }
 
-       fprintf (fh, "GETVAL %s/%s\n", hostname_g, value_string_g);
-       fflush (fh);
+       fh_out = fdopen (fd, "w");
+       if (fh_out == NULL)
+       {
+               fprintf (stderr, "fdopen failed: %s\n",
+                               strerror (errno));
+               fclose (fh_in);
+               return (-1);
+       }
 
-       if (fgets (buffer, sizeof (buffer), fh) == NULL)
+       fprintf (fh_out, "GETVAL %s/%s\n", hostname_g, value_string_g);
+       fflush (fh_out);
+
+       if (fgets (buffer, sizeof (buffer), fh_in) == NULL)
        {
                fprintf (stderr, "fgets failed: %s\n",
                                strerror (errno));
-               close (fd);
+               fclose (fh_in);
+               fclose (fh_out);
                return (-1);
        }
-       close (fd); fd = -1;
 
-       values_num = atoi (buffer);
-       if (values_num < 1)
-               return (-1);
+       {
+               char *ptr = strchr (buffer, ' ');
+
+               if (ptr != NULL)
+                       *ptr = '\0';
+
+               values_num = atoi (buffer);
+               if (values_num < 1)
+                       return (-1);
+       }
 
        values = (double *) malloc (values_num * sizeof (double));
        if (values == NULL)
@@ -214,32 +230,33 @@ static int get_values (int *ret_values_num, double **ret_values,
                return (-1);
        }
 
+       i = 0;
+       while (fgets (buffer, sizeof (buffer), fh_in) != NULL)
        {
-               char *ptr = strchr (buffer, ' ') + 1;
                char *key;
                char *value;
 
-               i = 0;
-               while ((key = strtok (ptr, " \t")) != NULL)
-               {
-                       ptr = NULL;
-                       value = strchr (key, '=');
-                       if (value == NULL)
-                               continue;
-                       *value = '\0'; value++;
+               key = buffer;
 
-                       if (ignore_ds (key) != 0)
-                               continue;
+               value = strchr (key, '=');
+               if (value == NULL)
+                       continue;
+               *value = '\0'; value++;
 
-                       values_names[i] = strdup (key);
-                       values[i] = atof (value);
+               if (ignore_ds (key) != 0)
+                       continue;
 
-                       i++;
-                       if (i >= values_num)
-                               break;
-               }
-               values_num = i;
+               values_names[i] = strdup (key);
+               values[i] = atof (value);
+
+               i++;
+               if (i >= values_num)
+                       break;
        }
+       values_num = i;
+
+       fclose (fh_in); fh_in = NULL; fd = -1;
+       fclose (fh_out); fh_out = NULL;
 
        *ret_values_num = values_num;
        *ret_values = values;
index 4a01d14..8cdf08b 100644 (file)
@@ -67,7 +67,7 @@ registered by the plugins. Any plugin basically consists of the implementation
 of these callback functions and initializing code which registers the
 functions with collectd. See the section "EXAMPLES" below for a really basic
 example. The following types of B<callback functions> are known to collectd
-(all of these are optional):
+(all of them are optional):
 
 =over 4
 
@@ -92,6 +92,12 @@ amount of time until it returns B<true> again.
 This type of function is used to write the dispatched values. It is called
 once for each call to B<plugin_dispatch_values>.
 
+=item flush functions
+
+This type of function is used to flush internal caches of plugins. It is
+usually triggered by the user only. Any plugin which caches data before
+writing it to disk should provide this kind of callback function.
+
 =item log functions
 
 This type of function is used to pass messages of plugins or the daemon itself
@@ -203,6 +209,8 @@ I<type> can be one of:
 
 =item TYPE_WRITE
 
+=item TYPE_FLUSH
+
 =item TYPE_LOG
 
 =item TYPE_NOTIF
@@ -245,13 +253,18 @@ arguments:
 
 =item TYPE_SHUTDOWN
 
-No arguments are passed
+No arguments are passed.
 
 =item TYPE_WRITE
 
 The arguments passed are I<type>, I<data-set>, and I<value-list>. I<type> is a
 string. For the layout of I<data-set> and I<value-list> see above.
 
+=item TYPE_FLUSH
+
+The only argument passed is I<timeout> which indicates that only data older
+than I<timeout> seconds is to be flushed.
+
 =item TYPE_LOG
 
 The arguments are I<log-level> and I<message>. The log level is small for
@@ -279,6 +292,22 @@ is found (and the number of values matches the number of data-sources) then the
 type, data-set and value-list is passed to all write-callbacks that are
 registered with the daemon.
 
+=item B<plugin_flush> ([B<timeout> => I<timeout>,] [B<plugins> => I<...>])
+
+Flush one or more plugins. I<timeout> is passed on to the registered
+flush-callbacks. If omitted, C<-1> is used. If the I<plugins> argument has
+been specified, only named plugins will be flushed. The argument's value may
+either be a string or a reference to an array of strings.
+
+=item B<plugin_flush_one> (I<timeout>, I<plugin>)
+
+This is identical to using "plugin_flush (timeout =E<gt> I<timeout>, plugins
+=E<gt> I<plugin>".
+
+=item B<plugin_flush_all> (I<timeout>)
+
+This is identical to using "plugin_flush (timeout =E<gt> I<timeout>)".
+
 =item B<plugin_dispatch_notification> (I<notification>)
 
 Submits a I<notification> to the daemon which will then pass it to all
@@ -332,6 +361,12 @@ available (B<:all> will export all of them):
 
 =item B<plugin_dispatch_values> ()
 
+=item B<plugin_flush> ()
+
+=item B<plugin_flush_one> ()
+
+=item B<plugin_flush_all> ()
+
 =item B<plugin_dispatch_notification> ()
 
 =item B<plugin_log> ()
@@ -348,6 +383,8 @@ available (B<:all> will export all of them):
 
 =item B<TYPE_WRITE>
 
+=item B<TYPE_FLUSH>
+
 =item B<TYPE_SHUTDOWN>
 
 =item B<TYPE_LOG>
@@ -504,6 +541,18 @@ instead.
 
 =back
 
+=head1 KNOWN BUGS
+
+=over 4
+
+=item
+
+Currently, it is not possible to flush a single Perl plugin only. You can
+either flush all Perl plugins or none at all and you have to use C<perl> as
+plugin name when doing so.
+
+=back
+
 =head1 SEE ALSO
 
 L<collectd(1)>,
index 3ef2438..971cb36 100644 (file)
@@ -29,6 +29,18 @@ Upon start the C<unixsock plugin> opens a UNIX-socket and waits for
 connections. Once a connection is established the client can send commands to
 the daemon which it will answer, if it understand them.
 
+In general the plugin answers with a status line of the following form:
+
+I<Status> I<Message>
+
+If I<Status> is greater than or equal to zero the message indicates success,
+if I<Status> is less than zero the message indicates failure. I<Message> is a
+human-readable string that further describes the return value.
+
+On success, I<Status> furthermore indicates the number of subsequent lines of
+output (not including the status line). Each such lines usually contains a
+single return value. See the description of each command for details.
+
 The following commands are implemented:
 
 =over 4
@@ -36,37 +48,34 @@ The following commands are implemented:
 =item B<GETVAL> I<Identifier>
 
 If the value identified by I<Identifier> (see below) is found the complete
-value-list is returned. The response is a space separated list of
-name-value-pairs:
-
-I<num> I<name>B<=>I<value>[ I<name>B<=>I<value>[ ...]]
-
-If I<num> is less then zero, an error occurred. Otherwise it contains the
-number of values that follow. Each value is of the form I<name>B<=>I<value>.
+value-list is returned. The response is a list of name-value-pairs, each pair
+on its own line (the number of lines is indicated by the status line - see
+above). Each name-value-pair is of the form I<name>B<=>I<value>.
 Counter-values are converted to a rate, e.E<nbsp>g. bytes per second.
 Undefined values are returned as B<NaN>.
 
 Example:
   -> | GETVAL myhost/cpu-0/cpu-user
-  <- | 1 value=1.260000e+00
+  <- | 1 Value found
+  <- | value=1.260000e+00
 
 =item B<LISTVAL>
 
 Returns a list of the values available in the value cache together with the
 time of the last update, so that querying applications can issue a B<GETVAL>
-command for the values that have changed.
-
-The first line's status number is the number of identifiers returned or less
-than zero if an error occurred. Each of the following lines contains the
-update time as an epoch value and the identifier, separated by a space.
+command for the values that have changed. Each return value consists of the
+update time as an epoch value and the identifier, separated by a space. The
+update time is the time of the last value, as provided by the collecting
+instance and may be very different from the time the server considers to be
+"now".
 
 Example:
   -> | LISTVAL
   <- | 69 Values found
-  <- | 1182204284 leeloo/cpu-0/cpu-idle
-  <- | 1182204284 leeloo/cpu-0/cpu-nice
-  <- | 1182204284 leeloo/cpu-0/cpu-system
-  <- | 1182204284 leeloo/cpu-0/cpu-user
+  <- | 1182204284 myhost/cpu-0/cpu-idle
+  <- | 1182204284 myhost/cpu-0/cpu-nice
+  <- | 1182204284 myhost/cpu-0/cpu-system
+  <- | 1182204284 myhost/cpu-0/cpu-user
   ...
 
 =item B<PUTVAL> I<Identifier> [I<OptionList>] I<Valuelist>
@@ -174,6 +183,20 @@ Example:
   -> | PUTNOTIF type=temperature severity=warning time=1201094702 message=The roof is on fire!
   <- | 0 Success
 
+=item B<FLUSH> [B<timeout=>I<Timeout>] [B<plugin=>I<Plugin> [...]]
+
+Flushes all cached data older than I<Timeout> seconds. If no timeout has been
+specified, it defaults to -1 which causes all data to be flushed. B<timeout>
+may be specified multiple times - each occurrence applies to plugins listed
+afterwards.
+
+If specified, only specific plugins are flushed. Otherwise all plugins
+providing a flush callback are flushed.
+
+Example:
+  -> | FLUSH
+  <- | 0 Done
+
 =back
 
 =head2 Identifiers
@@ -191,20 +214,6 @@ some examples:
   myhost/memory/memory-used
   myhost/disk-sda/disk_octets
 
-=head2 Return values
-
-Unless otherwise noted the plugin answers with a line of the following form:
-
-I<Num> I<Message>
-
-If I<Num> is zero the message indicates success, if I<Num> is non-zero the
-message indicates failure. I<Message> is a human-readable string that describes
-the return value further.
-
-Commands that return values may use I<Num> to return the number of values that
-follow, such as the B<GETVAL> command. These commands usually return a negative
-value on failure and never return zero.
-
 =head1 ABSTRACTION LAYER
 
 B<collectd> ships the Perl-Module L<Collectd::Unixsock> which
index 4e521f9..a3f63b4 100644 (file)
@@ -27,6 +27,8 @@
 #include <sys/socket.h>
 #include <netdb.h>
 
+#include <pthread.h>
+
 #include "plugin.h"
 #include "configfile.h"
 
@@ -45,16 +47,37 @@ kstat_ctl_t *kc;
 
 static int loop = 0;
 
-static void sigIntHandler (int signal)
+static void *do_flush (void *arg)
+{
+       INFO ("Flushing all data.");
+       plugin_flush_all (-1);
+       INFO ("Finished flushing all data.");
+       pthread_exit (NULL);
+       return NULL;
+}
+
+static void sig_int_handler (int signal)
 {
        loop++;
 }
 
-static void sigTermHandler (int signal)
+static void sig_term_handler (int signal)
 {
        loop++;
 }
 
+static void sig_usr1_handler (int signal)
+{
+       pthread_t      thread;
+       pthread_attr_t attr;
+
+       /* flushing the data might take a while,
+        * so it should be done asynchronously */
+       pthread_attr_init (&attr);
+       pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
+       pthread_create (&thread, &attr, do_flush, NULL);
+}
+
 static int init_hostname (void)
 {
        const char *str;
@@ -231,11 +254,13 @@ static void exit_usage (void)
                        "  General:\n"
                        "    -C <file>       Configuration file.\n"
                        "                    Default: "CONFIGFILE"\n"
+                       "    -t              Test config and exit.\n"
                        "    -P <file>       PID-file.\n"
                        "                    Default: "PIDFILE"\n"
 #if COLLECT_DAEMON
                        "    -f              Don't fork to the background.\n"
 #endif
+                       "    -h              Display help (this message)\n"
                        "\nBuiltin defaults:\n"
                        "  Config-File       "CONFIGFILE"\n"
                        "  PID-File          "PIDFILE"\n"
@@ -369,13 +394,15 @@ static int pidfile_remove (void)
 
 int main (int argc, char **argv)
 {
-       struct sigaction sigIntAction;
-       struct sigaction sigTermAction;
+       struct sigaction sig_int_action;
+       struct sigaction sig_term_action;
+       struct sigaction sig_usr1_action;
+       struct sigaction sig_pipe_action;
        char *configfile = CONFIGFILE;
        int test_config  = 0;
        const char *basedir;
 #if COLLECT_DAEMON
-       struct sigaction sigChldAction;
+       struct sigaction sig_chld_action;
        pid_t pid;
        int daemonize    = 1;
 #endif
@@ -460,9 +487,9 @@ int main (int argc, char **argv)
        /*
         * fork off child
         */
-       memset (&sigChldAction, '\0', sizeof (sigChldAction));
-       sigChldAction.sa_handler = SIG_IGN;
-       sigaction (SIGCHLD, &sigChldAction, NULL);
+       memset (&sig_chld_action, '\0', sizeof (sig_chld_action));
+       sig_chld_action.sa_handler = SIG_IGN;
+       sigaction (SIGCHLD, &sig_chld_action, NULL);
 
        if (daemonize)
        {
@@ -512,16 +539,39 @@ int main (int argc, char **argv)
        } /* if (daemonize) */
 #endif /* COLLECT_DAEMON */
 
+       memset (&sig_pipe_action, '\0', sizeof (sig_pipe_action));
+       sig_pipe_action.sa_handler = SIG_IGN;
+       sigaction (SIGPIPE, &sig_pipe_action, NULL);
+
        /*
         * install signal handlers
         */
-       memset (&sigIntAction, '\0', sizeof (sigIntAction));
-       sigIntAction.sa_handler = sigIntHandler;
-       sigaction (SIGINT, &sigIntAction, NULL);
+       memset (&sig_int_action, '\0', sizeof (sig_int_action));
+       sig_int_action.sa_handler = sig_int_handler;
+       if (0 != sigaction (SIGINT, &sig_int_action, NULL)) {
+               char errbuf[1024];
+               ERROR ("Error: Failed to install a signal handler for signal INT: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (1);
+       }
+
+       memset (&sig_term_action, '\0', sizeof (sig_term_action));
+       sig_term_action.sa_handler = sig_term_handler;
+       if (0 != sigaction (SIGTERM, &sig_term_action, NULL)) {
+               char errbuf[1024];
+               ERROR ("Error: Failed to install a signal handler for signal TERM: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (1);
+       }
 
-       memset (&sigTermAction, '\0', sizeof (sigTermAction));
-       sigTermAction.sa_handler = sigTermHandler;
-       sigaction (SIGTERM, &sigTermAction, NULL);
+       memset (&sig_usr1_action, '\0', sizeof (sig_usr1_action));
+       sig_usr1_action.sa_handler = sig_usr1_handler;
+       if (0 != sigaction (SIGUSR1, &sig_usr1_action, NULL)) {
+               char errbuf[1024];
+               ERROR ("Error: Failed to install a signal handler for signal USR1: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (1);
+       }
 
        /*
         * run the actual loops
index fb5f7a9..829d3e5 100644 (file)
@@ -13,9 +13,23 @@ FQDNLookup   true
 #Interval     10
 #ReadThreads  5
 
+@BUILD_PLUGIN_LOGFILE_TRUE@LoadPlugin logfile
+@BUILD_PLUGIN_SYSLOG_TRUE@LoadPlugin syslog
+
+#<Plugin logfile>
+#      LogLevel info
+#      File STDOUT
+#      Timestamp true
+#</Plugin>
+
+#<Plugin syslog>
+#      LogLevel info
+#</Plugin>
+
 @BUILD_PLUGIN_APACHE_TRUE@LoadPlugin apache
 @BUILD_PLUGIN_APCUPS_TRUE@LoadPlugin apcups
 @BUILD_PLUGIN_APPLE_SENSORS_TRUE@LoadPlugin apple_sensors
+@BUILD_PLUGIN_ASCENT_TRUE@LoadPlugin ascent
 @BUILD_PLUGIN_BATTERY_TRUE@LoadPlugin battery
 @BUILD_PLUGIN_CPU_TRUE@LoadPlugin cpu
 @BUILD_PLUGIN_CPUFREQ_TRUE@LoadPlugin cpufreq
@@ -29,11 +43,11 @@ FQDNLookup   true
 @BUILD_PLUGIN_HDDTEMP_TRUE@LoadPlugin hddtemp
 @BUILD_PLUGIN_INTERFACE_TRUE@LoadPlugin interface
 @BUILD_PLUGIN_IPTABLES_TRUE@LoadPlugin iptables
+@BUILD_PLUGIN_IPMI_TRUE@LoadPlugin ipmi
 @BUILD_PLUGIN_IPVS_TRUE@LoadPlugin ipvs
 @BUILD_PLUGIN_IRQ_TRUE@LoadPlugin irq
 @BUILD_PLUGIN_LIBVIRT_TRUE@LoadPlugin libvirt
 @BUILD_PLUGIN_LOAD_TRUE@LoadPlugin load
-@BUILD_PLUGIN_LOGFILE_TRUE@LoadPlugin logfile
 @BUILD_PLUGIN_MBMON_TRUE@LoadPlugin mbmon
 @BUILD_PLUGIN_MEMCACHED_TRUE@LoadPlugin memcached
 @BUILD_PLUGIN_MEMORY_TRUE@LoadPlugin memory
@@ -47,18 +61,21 @@ FQDNLookup   true
 @BUILD_PLUGIN_NUT_TRUE@LoadPlugin nut
 @BUILD_PLUGIN_PERL_TRUE@LoadPlugin perl
 @BUILD_PLUGIN_PING_TRUE@LoadPlugin ping
+@BUILD_PLUGIN_POWERDNS_TRUE@LoadPlugin powerdns
 @BUILD_PLUGIN_PROCESSES_TRUE@LoadPlugin processes
 @BUILD_PLUGIN_RRDTOOL_TRUE@LoadPlugin rrdtool
 @BUILD_PLUGIN_SENSORS_TRUE@LoadPlugin sensors
 @BUILD_PLUGIN_SERIAL_TRUE@LoadPlugin serial
 @BUILD_PLUGIN_SNMP_TRUE@LoadPlugin snmp
 @BUILD_PLUGIN_SWAP_TRUE@LoadPlugin swap
-@BUILD_PLUGIN_SYSLOG_TRUE@LoadPlugin syslog
+@BUILD_PLUGIN_TAIL_TRUE@LoadPlugin tail
 @BUILD_PLUGIN_TAPE_TRUE@LoadPlugin tape
 @BUILD_PLUGIN_TCPCONNS_TRUE@LoadPlugin tcpconns
+@BUILD_PLUGIN_TEAMSPEAK2_TRUE@LoadPlugin teamspeak2
 @BUILD_PLUGIN_UNIXSOCK_TRUE@LoadPlugin unixsock
 @BUILD_PLUGIN_USERS_TRUE@LoadPlugin users
 @BUILD_PLUGIN_UUID_TRUE@LoadPlugin uuid
+@BUILD_PLUGIN_VMEM_TRUE@LoadPlugin vmem
 @BUILD_PLUGIN_VSERVER_TRUE@LoadPlugin vserver
 @BUILD_PLUGIN_WIRELESS_TRUE@LoadPlugin wireless
 @BUILD_PLUGIN_XMMS_TRUE@LoadPlugin xmms
@@ -75,6 +92,13 @@ FQDNLookup   true
 #      Port "3551"
 #</Plugin>
 
+#<Plugin ascent>
+#      URL "http://localhost/ascent/status/"
+#      User "www-user"
+#      Password "secret"
+#      CACert "/etc/ssl/ca.crt"
+#</Plugin>
+
 #<Plugin csv>
 #      DataDir "@prefix@/var/lib/@PACKAGE_NAME@/csv"
 #      StoreRates false
@@ -88,6 +112,11 @@ FQDNLookup   true
 #      IgnoreSelected false
 #</Plugin>
 
+#<Plugin disk>
+#      Disk "/^[hs]d[a-f][0-9]?$/"
+#      IgnoreSelected false
+#</Plugin>
+
 #<Plugin dns>
 #      Interface "eth0"
 #      IgnoreSource "192.168.0.1"
@@ -137,12 +166,6 @@ FQDNLookup   true
 #      HostnameFormat name
 #</Plugin>
 
-#<Plugin logfile>
-#      LogLevel info
-#      File STDOUT
-#      Timestamp true
-#</Plugin>
-
 #<Plugin mbmon>
 #      Host "127.0.0.1"
 #      Port "411"
@@ -208,6 +231,20 @@ FQDNLookup   true
 #      TTL 255
 #</Plugin>
 
+#<Plugin powerdns>
+#  <Server "server_name">
+#    Collect "latency"
+#    Collect "udp-answers" "udp-queries"
+#    Socket "/var/run/pdns.controlsocket"
+#  </Server>
+#  <Recursor "recursor_name">
+#    Collect "questions"
+#    Collect "cache-hits" "cache-misses"
+#    Socket "/var/run/pdns_recursor.controlsocket"
+#  </Recursor>
+#  LocalSocket "/opt/collectd/var/run/collectd-powerdns"
+#</Plugin>
+
 #<Plugin processes>
 #      Process "name"
 #</Plugin>
@@ -267,8 +304,22 @@ FQDNLookup   true
 #   </Host>
 #</Plugin>
 
-#<Plugin syslog>
-#      LogLevel info
+#<Plugin "tail">
+#  <File "/var/log/exim4/mainlog">
+#    Instance "exim"
+#    <Match>
+#      Regex "S=([1-9][0-9]*)"
+#      DSType "CounterAdd"
+#      Type "ipt_bytes"
+#      Instance "total"
+#    </Match>
+#    <Match>
+#      Regex "\\<R=local_user\\>"
+#      DSType "CounterInc"
+#      Type "email_count"
+#      Instance "local_user"
+#    </Match>
+#  </File>
 #</Plugin>
 
 #<Plugin tcpconns>
@@ -277,6 +328,12 @@ FQDNLookup   true
 #      RemotePort "25"
 #</Plugin>
 
+#<Plugin teamspeak2>
+#      Host "127.0.0.1"
+#      Port "51234"
+#      Server "8767"
+#</Plugin>
+
 #<Plugin unixsock>
 #      SocketFile "@prefix@/var/run/@PACKAGE_NAME@-unixsock"
 #      SocketGroup "collectd"
@@ -287,3 +344,7 @@ FQDNLookup   true
 #      UUIDFile "/etc/uuid"
 #</Plugin>
 
+#<Plugin vmem>
+#      Verbose false
+#</Plugin>
+
index aa4421d..c02dfa8 100644 (file)
@@ -173,6 +173,19 @@ Optional user name needed for authentication.
 
 Optional password needed for authentication.
 
+=item B<VerifyPeer> B<true|false>
+
+Enable or disable peer SSL certificate verification. See
+L<http://curl.haxx.se/docs/sslcerts.html> for details. Enabled by default.
+
+=item B<VerifyHost> B<true|false>
+
+Enable or disable peer host name verification. If enabled, the plugin checks
+if the C<Common Name> or a C<Subject Alternate Name> field of the SSL
+certificate matches the host name provided by the B<URL> option. If this
+identity check fails, the connection is aborted. Obviously, only works when
+connecting to a SSL enabled server. Enabled by default.
+
 =item B<CACert> I<File>
 
 File that holds one or more SSL certificates. If you want to use HTTPS you will
@@ -197,6 +210,36 @@ TCP-Port to connect to. Defaults to B<3551>.
 
 =back
 
+=head2 Plugin C<ascent>
+
+This plugin collects information about an Ascent server, a free server for the
+"World of Warcraft" game. This plugin gathers the information by fetching the
+XML status page using C<libcurl> and parses it using C<libxml2>.
+
+The configuration options are the same as for the C<apache> plugin above:
+
+=over 4
+
+=item B<URL> I<http://localhost/ascent/status/>
+
+Sets the URL of the XML status output.
+
+=item B<User> I<Username>
+
+Optional user name needed for authentication.
+
+=item B<Password> I<Password>
+
+Optional password needed for authentication.
+
+=item B<CACert> I<File>
+
+File that holds one or more SSL certificates. If you want to use HTTPS you will
+possibly need this option. What CA certificates come bundled with C<libcurl>
+and are checked by default depends on the distribution you use.
+
+=back
+
 =head2 Plugin C<cpufreq>
 
 This plugin doesn't have any options. It reads
@@ -247,6 +290,40 @@ at all, B<all> partitions are selected.
 
 =back
 
+=head2 Plugin C<disk>
+
+The C<disk> plugin collects information about the usage of physical disks and
+logical disks (partitions). Values collected are the number of octets written
+to and read from a disk or partition, the number of read/write operations
+issued to the disk and a rather complex "time" it took for these commands to be
+issued.
+
+Using the following two options you can ignore some disks or configure the
+collection only of specific disks.
+
+=over 4
+
+=item B<Disk> I<Name>
+
+Select the disk I<Name>. Whether it is collected or ignored depends on the
+B<IgnoreSelected> setting, see below. As with other plugins that use the
+daemon's ignorelist functionality, a string that starts and ends with a slash
+is interpreted as a regular expression. Examples:
+
+  Disk "sdd"
+  Disk "/hda[34]/"
+
+=item B<IgnoreSelected> B<true>|B<false>
+
+Sets whether selected disks, i.E<nbsp>e. the ones matches by any of the B<Disk>
+statements, are ignored or if all other disks are ignored. The behavior
+(hopefully) is intuitive: If no B<Disk> option is configured, all disks are
+collected. If at least one B<Disk> option is given and no B<IgnoreSelected> or
+set to B<false>, B<only> matching disks will be collected. If B<IgnoreSelected>
+is set to B<true>, all disks are collected B<except> the ones matched.
+
+=back
+
 =head2 Plugin C<dns>
 
 =over 4
@@ -378,6 +455,24 @@ other interfaces are collected.
 
 =back
 
+=head2 Plugin C<ipmi>
+
+=over 4
+
+=item B<Sensor> I<Sensor>
+
+Selects sensors to collect or to ignore, depending on B<IgnoreSelected>.
+
+=item B<IgnoreSelected> I<true>|I<false>
+
+If no configuration if given, the B<ipmi> plugin will collect data from all
+sensors found of type "temperature", "voltage", "current" and "fanspeed".
+This option enables you to do that: By setting B<IgnoreSelected> to I<true>
+the effect of B<Sensor> is inverted: All selected sensors are ignored and
+all other sensors are collected.
+
+=back
+
 =head2 Plugin C<iptables>
 
 =over 4
@@ -759,6 +854,19 @@ Optional user name needed for authentication.
 
 Optional password needed for authentication.
 
+=item B<VerifyPeer> B<true|false>
+
+Enable or disable peer SSL certificate verification. See
+L<http://curl.haxx.se/docs/sslcerts.html> for details. Enabled by default.
+
+=item B<VerifyHost> B<true|false>
+
+Enable or disable peer host name verification. If enabled, the plugin checks
+if the C<Common Name> or a C<Subject Alternate Name> field of the SSL
+certificate matches the host name provided by the B<URL> option. If this
+identity check fails, the connection is aborted. Obviously, only works when
+connecting to a SSL enabled server. Enabled by default.
+
 =item B<CACert> I<File>
 
 File that holds one or more SSL certificates. If you want to use HTTPS you will
@@ -819,6 +927,139 @@ Sets the Time-To-Live of generated ICMP packets.
 
 =back
 
+=head2 Plugin C<powerdns>
+
+The C<powerdns> plugin queries statistics from an authoritative PowerDNS
+nameserver and/or a PowerDNS recursor. Since both offer a wide variety of
+values, many of which are probably meaningless to most users, but may be useful
+for some. So you may chose which values to collect, but if you don't, some
+reasonable defaults will be collected.
+
+  <Plugin "powerdns">
+    <Server "server_name">
+      Collect "latency"
+      Collect "udp-answers" "udp-queries"
+      Socket "/var/run/pdns.controlsocket"
+    </Server>
+    <Recursor "recursor_name">
+      Collect "questions"
+      Collect "cache-hits" "cache-misses"
+      Socket "/var/run/pdns_recursor.controlsocket"
+    </Recursor>
+    LocalSocket "/opt/collectd/var/run/collectd-powerdns"
+  </Plugin>
+
+=over 4
+
+=item B<Server> and B<Recursor> block
+
+The B<Server> block defines one authoritative server to query, the B<Recursor>
+does the same for an recursing server. The possible options in both blocks are
+the same, though. The argument defines a name for the serverE<nbsp>/ recursor
+and is required.
+
+=over 4
+
+=item B<Collect> I<Field>
+
+Using the B<Collect> statement you can select which values to collect. Here,
+you specify the name of the values as used by the PowerDNS servers, e.E<nbsp>g.
+C<dlg-only-drops>, C<answers10-100>.
+
+The method of getting the values differs for B<Server> and B<Recursor> blocks:
+When querying the server a C<SHOW *> command is issued in any case, because
+that's the only way of getting multiple values out of the server at once.
+collectd then picks out the values you have selected. When querying the
+recursor, a command is generated to query exactly these values. So if you
+specify invalid fields when querying the recursor, a syntax error may be
+returned by the daemon and collectd may not collect any values at all.
+
+If no B<Collect> statement is given, the following B<Server> values will be
+collected:
+
+=over 4
+
+=item latency
+
+=item packetcache-hit
+
+=item packetcache-miss
+
+=item packetcache-size
+
+=item query-cache-hit
+
+=item query-cache-miss
+
+=item recursing-answers
+
+=item recursing-questions
+
+=item tcp-answers
+
+=item tcp-queries
+
+=item udp-answers
+
+=item udp-queries
+
+=back
+
+The following B<Recursor> values will be collected by default:
+
+=over 4
+
+=item noerror-answers
+
+=item nxdomain-answers
+
+=item servfail-answers
+
+=item sys-msec
+
+=item user-msec
+
+=item qa-latency
+
+=item cache-entries
+
+=item cache-hits
+
+=item cache-misses
+
+=item questions
+
+=back
+
+Please note that up to that point collectd doesn't know what values are
+available on the server and values that are added do not need a change of the
+mechanism so far. However, the values must be mapped to collectd's naming
+scheme, which is done using a lookup table that lists all known values. If
+values are added in the future and collectd does not know about them, you will
+get an error much like this:
+
+  powerdns plugin: submit: Not found in lookup table: foobar = 42
+
+In this case please file a bug report with the collectd team.
+
+=item B<Socket> I<Path>
+
+Configures the path to the UNIX domain socket to be used when connecting to the
+daemon. By default C</var/run/pdns.controlsocket> will be used for an
+authoritative server and C</var/run/pdns_recursor.controlsocket> will be used
+for the recursor.
+
+=back
+
+=item B<LocalSocket> I<Path>
+
+Querying the recursor is done using UDP. When using UDP over UNIX domain
+sockets, the client socket needs a name in the file system, too. You can set
+this local name to I<Path> using the B<LocalSocket> option. The default is
+C<I<prefix>/var/run/collectd-powerdns>.
+
+=back
+
 =head2 Plugin C<processes>
 
 =over 4
@@ -963,6 +1204,145 @@ debugging support.
 
 =back
 
+=head2 Plugin C<tail>
+
+The C<tail plugin> plugins follows logfiles, just like L<tail(1)> does, parses
+each line and dispatches found values. What is matched can be configured by the
+user using (extended) regular expressions, as described in L<regex(7)>.
+
+  <Plugin "tail">
+    <File "/var/log/exim4/mainlog">
+      Instance "exim"
+      <Match>
+       Regex "S=([1-9][0-9]*)"
+       DSType "CounterAdd"
+       Type "ipt_bytes"
+       Instance "total"
+      </Match>
+      <Match>
+       Regex "\\<R=local_user\\>"
+       DSType "CounterInc"
+       Type "counter"
+       Instance "local_user"
+      </Match>
+    </File>
+  </Plugin>
+
+The config consists of one or more B<File> blocks, each of which configures one
+logfile to parse. Within each B<File> block, there are one or more B<Match>
+blocks, which configure a regular expression to search for.
+
+The B<Instance> option in the B<File> block may be used to set the plugin
+instance. So in the above example the plugin name C<tail-foo> would be used.
+This plugin instance is for all B<Match> blocks that B<follow> it, until the
+next B<Instance> option. This way you can extract several plugin instances from
+one logfile, handy when parsing syslog and the like.
+
+Each B<Match> block has the following options to describe how the match should
+be performed:
+
+=over 4
+
+=item B<Regex> I<regex>
+
+Sets the regular expression to use for matching against a line. The first
+subexpression has to match something that can be turned into a number by
+L<strtoll(3)> or L<strtod(3)>, depending on the value of C<CounterAdd>, see
+below. Because B<extended> regular expressions are used, you do not need to use
+backslashes for subexpressions! If in doubt, please consult L<regex(7)>. Due to
+collectd's config parsing you need to escape backslashes, though. So if you
+want to match literal parentheses you need to do the following:
+
+  Regex "SPAM \\(Score: (-?[0-9]+\\.[0-9]+)\\)"
+
+=item B<DSType> I<Type>
+
+Sets how the values are cumulated. I<Type> is one of:
+
+=over 4
+
+=item B<GaugeAverage>
+
+Calculate the average.
+
+=item B<GaugeMin>
+
+Use the smallest number only.
+
+=item B<GaugeMax>
+
+Use the greatest number only.
+
+=item B<GaugeLast>
+
+Use the last number found.
+
+=item B<CounterSet>
+
+The matched number is a counter. Simply sets the internal counter to this
+value.
+
+=item B<CounterAdd>
+
+Add the matched value to the internal counter.
+
+=item B<CounterInc>
+
+Increase the internal counter by one. This B<DSType> is the only one that does
+not use the matched subexpression, but simply counts the number of matched
+lines. Thus, you may use a regular expression without submatch in this case.
+
+=back
+
+As you'd expect the B<Gauge*> types interpret the submatch as a floating point
+number, using L<strtod(3)>. The B<CounterSet> and B<CounterAdd> interpret the
+submatch as an integer using L<strtoll(3)>. B<CounterInc> does not use the
+submatch at all and it may be omitted in this case.
+
+=item B<Type> I<Type>
+
+Sets the type used to dispatch this value. Detailed information about types and
+their configuration can be found in L<types.db(5)>.
+
+=item B<Instance> I<TypeInstance>
+
+This optional setting sets the type instance to use.
+
+=back
+
+=head2 Plugin C<teamspeak2>
+
+The C<teamspeak2 plugin> connects to the query port of a teamspeak2 server and
+polls interesting global and virtual server data. The plugin can query only one
+physical server but unlimited virtual servers. You can use the following
+options to configure it:
+
+=over 4
+
+=item B<Host> I<hostname/ip>
+
+The hostname or ip which identifies the physical server.
+Default: 127.0.0.1
+
+=item B<Port> I<port>
+
+The query port of the physical server. This needs to be a string.
+Default: "51234"
+
+=item B<Server> I<port>
+
+This option has to be added once for every virtual server the plugin should
+query. If you want to query the virtual server on port 8767 this is what the
+option would look like:
+
+  Server "8767"
+
+This option, although numeric, needs to be a string, i.E<nbsp>e. you B<must>
+use quotes around it! If no such statement is given only global information
+will be collected.
+
+=back
+
 =head2 Plugin C<tcpconns>
 
 The C<tcpconns plugin> counts the number of currently established TCP
@@ -1063,6 +1443,24 @@ Take the UUID from the given file (default I</etc/uuid>).
 
 =back
 
+=head2 Plugin C<vmem>
+
+The C<vmem> plugin collects information about the usage of virtual memory.
+Since the statistics provided by the Linux kernel are very detailed, they are
+collected very detailed. However, to get all the details, you have to switch
+them on manually. Most people just want an overview over, such as the number of
+pages read from swap space.
+
+=over 4
+
+=item B<Verbose> B<true>|B<false>
+
+Enables verbose collection of information. This will start collecting page
+"actions", e.E<nbsp>g. page allocations, (de)activations, steals and so on.
+Part of these statistics are collected on a "per zone" basis.
+
+=back
+
 =head2 Plugin C<vserver>
 
 This plugin doesn't have any options. B<VServer> support is only available for
@@ -1114,6 +1512,7 @@ information.
      Instance "eth0"
      <Type "if_octets">
        FailureMax 10000000
+       DataSource "rx"
      </Type>
    </Plugin>
 
@@ -1164,6 +1563,19 @@ infinity. If a value is less than B<FailureMin> a B<FAILURE> notification will
 be created. If the value is less than B<WarningMin> but greater than (or equal
 to) B<FailureMin> a B<WARNING> notification will be created.
 
+=item B<DataSource> I<DSName>
+
+Some data sets have more than one "data source". Interesting examples are the
+C<if_octets> data set, which has received (C<rx>) and sent (C<tx>) bytes and
+the C<disk_ops> data set, which holds C<read> and C<write> operations. The
+system load data set, C<load>, even has three data sources: C<shortterm>,
+C<midterm>, and C<longterm>.
+
+Normally, all data sources are checked against a configured threshold. If this
+is undesirable, or if you want to specify different limits for each data
+source, you can use the B<DataSource> option to have a threshold apply only to
+one data source.
+
 =item B<Invert> B<true>|B<false>
 
 If set to B<true> the range of acceptable values is inverted, i.E<nbsp>e.
index b55362a..c10ae78 100644 (file)
@@ -99,6 +99,25 @@ the daemon, have manpages of their own to describe their functionality in more
 detail. In particular those are L<collectd-email(5)>, L<collectd-exec(5)>,
 L<collectd-perl(5)>, L<collectd-snmp(5)>, and L<collectd-unixsock(5)>
 
+=head1 SIGNALS
+
+B<collectd> accepts the following signals:
+
+=over 4
+
+=item B<SIGINT>, B<SIGTERM>
+
+These signals cause B<collectd> to shut down all plugins and terminate.
+
+=item B<SIGUSR1>
+
+This signal causes B<collectd> to signal all plugins to flush data from
+internal caches. E.E<nbsp>g. the C<rrdtool plugin> will write all pending data
+to the RRD files. This is the same as using the C<FLUSH -1> command of the
+C<unixsock plugin>.
+
+=back
+
 =head1 SEE ALSO
 
 L<collectd.conf(5)>,
index 0daebcc..0fadc06 100644 (file)
--- a/src/cpu.c
+++ b/src/cpu.c
 # endif
 #endif /* HAVE_SYSCTLBYNAME */
 
-#if !PROCESSOR_CPU_LOAD_INFO && !KERNEL_LINUX && !HAVE_LIBKSTAT && !HAVE_SYSCTLBYNAME
+#if HAVE_STATGRAB_H
+# include <statgrab.h>
+#endif
+
+#if !PROCESSOR_CPU_LOAD_INFO && !KERNEL_LINUX && !HAVE_LIBKSTAT \
+       && !HAVE_SYSCTLBYNAME && !HAVE_LIBSTATGRAB
 # error "No applicable input method."
 #endif
 
@@ -98,7 +103,11 @@ static int numcpu;
 
 #elif defined(HAVE_SYSCTLBYNAME)
 static int numcpu;
-#endif /* HAVE_SYSCTLBYNAME */
+/* #endif HAVE_SYSCTLBYNAME */
+
+#elif defined(HAVE_LIBSTATGRAB)
+/* no variables needed */
+#endif /* HAVE_LIBSTATGRAB */
 
 static int init (void)
 {
@@ -152,7 +161,11 @@ static int init (void)
 
        if (numcpu != 1)
                NOTICE ("cpu: Only one processor supported when using `sysctlbyname' (found %i)", numcpu);
-#endif
+/* #endif HAVE_SYSCTLBYNAME */
+
+#elif defined(HAVE_LIBSTATGRAB)
+       /* nothing to initialize */
+#endif /* HAVE_LIBSTATGRAB */
 
        return (0);
 } /* int init */
@@ -370,7 +383,25 @@ static int cpu_read (void)
        submit (0, "nice", cpuinfo[CP_NICE]);
        submit (0, "system", cpuinfo[CP_SYS]);
        submit (0, "idle", cpuinfo[CP_IDLE]);
-#endif
+/* #endif HAVE_SYSCTLBYNAME */
+
+#elif defined(HAVE_LIBSTATGRAB)
+       sg_cpu_stats *cs;
+       cs = sg_get_cpu_stats ();
+
+       if (cs == NULL)
+       {
+              ERROR ("cpu plugin: sg_get_cpu_stats failed.");
+               return (-1);
+       }
+
+       submit (0, "idle",   (counter_t) cs->idle);
+       submit (0, "nice",   (counter_t) cs->nice);
+       submit (0, "swap",   (counter_t) cs->swap);
+       submit (0, "system", (counter_t) cs->kernel);
+       submit (0, "user",   (counter_t) cs->user);
+       submit (0, "wait",   (counter_t) cs->iowait);
+#endif /* HAVE_LIBSTATGRAB */
 
        return (0);
 }
index 536c511..20afd30 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * collectd - src/disk.c
- * Copyright (C) 2005-2007  Florian octo Forster
+ * Copyright (C) 2005-2008  Florian octo Forster
  *
  * 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
@@ -22,6 +22,7 @@
 #include "collectd.h"
 #include "common.h"
 #include "plugin.h"
+#include "utils_ignorelist.h"
 
 #if HAVE_MACH_MACH_TYPES_H
 #  include <mach/mach_types.h>
 #  define UINT_MAX 4294967295U
 #endif
 
+#if HAVE_STATGRAB_H
+# include <statgrab.h>
+#endif
+
 #if HAVE_IOKIT_IOKITLIB_H
 static mach_port_t io_master_port = MACH_PORT_NULL;
 /* #endif HAVE_IOKIT_IOKITLIB_H */
@@ -97,10 +102,50 @@ static kstat_t *ksp[MAX_NUMDISK];
 static int numdisk = 0;
 /* #endif HAVE_LIBKSTAT */
 
+#elif defined(HAVE_LIBSTATGRAB)
+/* #endif HAVE_LIBKSTATGRAB */
+
 #else
 # error "No applicable input method."
 #endif
 
+static const char *config_keys[] =
+{
+       "Disk",
+       "IgnoreSelected"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+
+static ignorelist_t *ignorelist = NULL;
+
+static int disk_config (const char *key, const char *value)
+{
+  if (ignorelist == NULL)
+    ignorelist = ignorelist_create (/* invert = */ 1);
+  if (ignorelist == NULL)
+    return (1);
+
+  if (strcasecmp ("Disk", key) == 0)
+  {
+    ignorelist_add (ignorelist, value);
+  }
+  else if (strcasecmp ("IgnoreSelected", key) == 0)
+  {
+    int invert = 1;
+    if ((strcasecmp ("True", value) == 0)
+       || (strcasecmp ("Yes", value) == 0)
+       || (strcasecmp ("On", value) == 0))
+      invert = 0;
+    ignorelist_set_invert (ignorelist, invert);
+  }
+  else
+  {
+    return (-1);
+  }
+
+  return (0);
+} /* int disk_config */
+
 static int disk_init (void)
 {
 #if HAVE_IOKIT_IOKITLIB_H
@@ -158,6 +203,10 @@ static void disk_submit (const char *plugin_instance,
        value_t values[2];
        value_list_t vl = VALUE_LIST_INIT;
 
+       /* Both `ignorelist' and `plugin_instance' may be NULL. */
+       if (ignorelist_match (ignorelist, plugin_instance) != 0)
+         return;
+
        values[0].counter = read;
        values[1].counter = write;
 
@@ -617,13 +666,31 @@ static int disk_read (void)
                                        kio.KIO_ROPS, kio.KIO_WOPS);
                }
        }
-#endif /* defined(HAVE_LIBKSTAT) */
+/* #endif defined(HAVE_LIBKSTAT) */
+
+#elif defined(HAVE_LIBSTATGRAB)
+       sg_disk_io_stats *ds;
+       int disks, counter;
+       char name[DATA_MAX_NAME_LEN];
+       
+       if ((ds = sg_get_disk_io_stats(&disks)) == NULL)
+               return (0);
+               
+       for (counter=0; counter < disks; counter++) {
+               strncpy(name, ds->disk_name, sizeof(name));
+               name[sizeof(name)-1] = '\0'; /* strncpy doesn't terminate longer strings */
+               disk_submit (name, "disk_octets", ds->read_bytes, ds->write_bytes);
+               ds++;
+       }
+#endif /* defined(HAVE_LIBSTATGRAB) */
 
        return (0);
 } /* int disk_read */
 
 void module_register (void)
 {
-       plugin_register_init ("disk", disk_init);
-       plugin_register_read ("disk", disk_read);
+  plugin_register_config ("disk", disk_config,
+      config_keys, config_keys_num);
+  plugin_register_init ("disk", disk_init);
+  plugin_register_read ("disk", disk_read);
 } /* void module_register */
index c78d761..0882ad4 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * collectd - src/email.c
- * Copyright (C) 2006,2007  Sebastian Harl
+ * Copyright (C) 2006-2008  Sebastian Harl
  *
  * 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
 #      include <grp.h>
 #endif /* HAVE_GRP_H */
 
-#define MODULE_NAME "email"
-
-/* 256 bytes ought to be enough for anybody ;-) */
-#define BUFSIZE 256
-
 #define SOCK_PATH LOCALSTATEDIR"/run/"PACKAGE_NAME"-email"
 #define MAX_CONNS 5
 #define MAX_CONNS_LIMIT 16384
 
-#define log_err(...) ERROR (MODULE_NAME": "__VA_ARGS__)
-#define log_warn(...) WARNING (MODULE_NAME": "__VA_ARGS__)
+#define log_debug(...) DEBUG ("email: "__VA_ARGS__)
+#define log_err(...) ERROR ("email: "__VA_ARGS__)
+#define log_warn(...) WARNING ("email: "__VA_ARGS__)
 
 /*
  * Private data structures
@@ -90,19 +86,15 @@ typedef struct collector {
        pthread_t thread;
 
        /* socket descriptor of the current/last connection */
-       int socket;
+       FILE *socket;
 } collector_t;
 
 /* linked list of pending connections */
 typedef struct conn {
        /* socket to read data from */
-       int socket;
-
-       /* buffer to read data to */
-       char *buffer;
-       int  idx; /* current write position in buffer */
-       int  length; /* length of the current line, i.e. index of '\0' */
+       FILE *socket;
 
+       /* linked list of connections */
        struct conn *next;
 } conn_t;
 
@@ -125,8 +117,8 @@ static const char *config_keys[] =
 static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
 
 /* socket configuration */
-static char *sock_file  = SOCK_PATH;
-static char *sock_group = COLLECTD_GRP_NAME;
+static char *sock_file  = NULL;
+static char *sock_group = NULL;
 static int  sock_perms  = S_IRWXU | S_IRWXG;
 static int  max_conns   = MAX_CONNS;
 
@@ -154,17 +146,20 @@ static pthread_mutex_t available_mutex = PTHREAD_MUTEX_INITIALIZER;
 static int available_collectors;
 
 static pthread_mutex_t count_mutex = PTHREAD_MUTEX_INITIALIZER;
-static type_list_t count;
+static type_list_t list_count;
+static type_list_t list_count_copy;
 
 static pthread_mutex_t size_mutex = PTHREAD_MUTEX_INITIALIZER;
-static type_list_t size;
+static type_list_t list_size;
+static type_list_t list_size_copy;
 
 static pthread_mutex_t score_mutex = PTHREAD_MUTEX_INITIALIZER;
 static double score;
 static int score_count;
 
 static pthread_mutex_t check_mutex = PTHREAD_MUTEX_INITIALIZER;
-static type_list_t check;
+static type_list_t list_check;
+static type_list_t list_check_copy;
 
 /*
  * Private functions
@@ -172,9 +167,13 @@ static type_list_t check;
 static int email_config (const char *key, const char *value)
 {
        if (0 == strcasecmp (key, "SocketFile")) {
+               if (NULL != sock_file)
+                       free (sock_file);
                sock_file = sstrdup (value);
        }
        else if (0 == strcasecmp (key, "SocketGroup")) {
+               if (NULL != sock_group)
+                       free (sock_group);
                sock_group = sstrdup (value);
        }
        else if (0 == strcasecmp (key, "SocketPerms")) {
@@ -241,140 +240,10 @@ static void type_list_incr (type_list_t *list, char *name, int incr)
        return;
 } /* static void type_list_incr (type_list_t *, char *) */
 
-/* Read a single character from the socket. If an error occurs or end-of-file
- * is reached return '\0'. */
-static char read_char (conn_t *src)
-{
-       char ret = '\0';
-
-       fd_set fdset;
-
-       FD_ZERO (&fdset);
-       FD_SET (src->socket, &fdset);
-
-       if (-1 == select (src->socket + 1, &fdset, NULL, NULL, NULL)) {
-               char errbuf[1024];
-               log_err ("select() failed: %s",
-                               sstrerror (errno, errbuf, sizeof (errbuf)));
-               return '\0';
-       }
-
-       assert (FD_ISSET (src->socket, &fdset));
-
-       do {
-               ssize_t len = 0;
-
-               errno = 0;
-               if (0 > (len = read (src->socket, (void *)&ret, 1))) {
-                       if (EINTR != errno) {
-                               char errbuf[1024];
-                               log_err ("read() failed: %s",
-                                               sstrerror (errno, errbuf, sizeof (errbuf)));
-                               return '\0';
-                       }
-               }
-
-               if (0 == len)
-                       return '\0';
-       } while (EINTR == errno);
-       return ret;
-} /* static char read_char (conn_t *) */
-
-/* Read a single line (terminated by '\n') from the the socket.
- *
- * The return value is zero terminated and does not contain any newline
- * characters.
- *
- * If an error occurs or end-of-file is reached return NULL.
- *
- * IMPORTANT NOTE: If there is no newline character found in BUFSIZE
- * characters of the input stream, the line will will be ignored! By
- * definition we should not get any longer input lines, thus this is
- * acceptable in this case ;-) */
-static char *read_line (conn_t *src)
-{
-       int i = 0;
-
-       assert ((BUFSIZE >= src->idx) && (src->idx >= 0));
-       assert ((src->idx > src->length) || (src->length == 0));
-
-       if (src->length > 0) { /* remove old line */
-               src->idx -= (src->length + 1);
-               memmove (src->buffer, src->buffer + src->length + 1, src->idx);
-               src->length = 0;
-       }
-
-       for (i = 0; i < src->idx; ++i) {
-               if ('\n' == src->buffer[i])
-                       break;
-       }
-
-       if (i == src->idx) {
-               fd_set fdset;
-
-               ssize_t len = 0;
-
-               FD_ZERO (&fdset);
-               FD_SET (src->socket, &fdset);
-
-               if (-1 == select (src->socket + 1, &fdset, NULL, NULL, NULL)) {
-                       char errbuf[1024];
-                       log_err ("select() failed: %s",
-                                       sstrerror (errno, errbuf, sizeof (errbuf)));
-                       return NULL;
-               }
-
-               assert (FD_ISSET (src->socket, &fdset));
-
-               do {
-                       errno = 0;
-                       if (0 > (len = read (src->socket,
-                                                       (void *)(src->buffer + src->idx),
-                                                       BUFSIZE - src->idx))) {
-                               if (EINTR != errno) {
-                                       char errbuf[1024];
-                                       log_err ("read() failed: %s",
-                                                       sstrerror (errno, errbuf, sizeof (errbuf)));
-                                       return NULL;
-                               }
-                       }
-
-                       if (0 == len)
-                               return NULL;
-               } while (EINTR == errno);
-
-               src->idx += len;
-
-               for (i = src->idx - len; i < src->idx; ++i) {
-                       if ('\n' == src->buffer[i])
-                               break;
-               }
-
-               if (i == src->idx) {
-                       src->length = 0;
-
-                       if (BUFSIZE == src->idx) { /* no space left in buffer */
-                               while ('\n' != read_char (src))
-                                       /* ignore complete line */;
-
-                               src->idx = 0;
-                       }
-                       return read_line (src);
-               }
-       }
-
-       src->buffer[i] = '\0';
-       src->length    = i;
-
-       return src->buffer;
-} /* static char *read_line (conn_t *) */
-
 static void *collect (void *arg)
 {
        collector_t *this = (collector_t *)arg;
 
-       char *buffer = (char *)smalloc (BUFSIZE);
-
        while (1) {
                int loop = 1;
 
@@ -393,44 +262,50 @@ static void *collect (void *arg)
                        conns.tail = NULL;
                }
 
-               this->socket = connection->socket;
-
                pthread_mutex_unlock (&conns_mutex);
 
-               connection->buffer = buffer;
-               connection->idx    = 0;
-               connection->length = 0;
+               /* make the socket available to the global
+                * thread and connection management */
+               this->socket = connection->socket;
 
-               { /* put the socket in non-blocking mode */
-                       int flags = 0;
+               log_debug ("collect: handling connection on fd #%i",
+                               fileno (this->socket));
 
-                       errno = 0;
-                       if (-1 == fcntl (connection->socket, F_GETFL, &flags)) {
-                               char errbuf[1024];
-                               log_err ("fcntl() failed: %s",
-                                               sstrerror (errno, errbuf, sizeof (errbuf)));
-                               loop = 0;
-                       }
+               while (loop) {
+                       /* 256 bytes ought to be enough for anybody ;-) */
+                       char line[256 + 1]; /* line + '\0' */
+                       int  len = 0;
 
                        errno = 0;
-                       if (-1 == fcntl (connection->socket, F_SETFL, flags | O_NONBLOCK)) {
-                               char errbuf[1024];
-                               log_err ("fcntl() failed: %s",
-                                               sstrerror (errno, errbuf, sizeof (errbuf)));
+                       if (NULL == fgets (line, sizeof (line), this->socket)) {
                                loop = 0;
+
+                               if (0 != errno) {
+                                       char errbuf[1024];
+                                       log_err ("collect: reading from socket (fd #%i) "
+                                                       "failed: %s", fileno (this->socket),
+                                                       sstrerror (errno, errbuf, sizeof (errbuf)));
+                               }
+                               break;
                        }
-               }
 
-               while (loop) {
-                       char *line = read_line (connection);
+                       len = strlen (line);
+                       if (('\n' != line[len - 1]) && ('\r' != line[len - 1])) {
+                               log_warn ("collect: line too long (> %zu characters): "
+                                               "'%s' (truncated)", sizeof (line) - 1, line);
 
-                       if (NULL == line) {
-                               loop = 0;
-                               break;
+                               while (NULL != fgets (line, sizeof (line), this->socket))
+                                       if (('\n' == line[len - 1]) || ('\r' == line[len - 1]))
+                                               break;
+                               continue;
                        }
 
+                       line[len - 1] = '\0';
+
+                       log_debug ("collect: line = '%s'", line);
+
                        if (':' != line[1]) {
-                               log_err ("syntax error in line '%s'", line);
+                               log_err ("collect: syntax error in line '%s'", line);
                                continue;
                        }
 
@@ -441,19 +316,19 @@ static void *collect (void *arg)
                                int  bytes = 0;
 
                                if (NULL == tmp) {
-                                       log_err ("syntax error in line '%s'", line);
+                                       log_err ("collect: syntax error in line '%s'", line);
                                        continue;
                                }
 
                                bytes = atoi (tmp);
 
                                pthread_mutex_lock (&count_mutex);
-                               type_list_incr (&count, type, 1);
+                               type_list_incr (&list_count, type, 1);
                                pthread_mutex_unlock (&count_mutex);
 
                                if (bytes > 0) {
                                        pthread_mutex_lock (&size_mutex);
-                                       type_list_incr (&size, type, bytes);
+                                       type_list_incr (&list_size, type, bytes);
                                        pthread_mutex_unlock (&size_mutex);
                                }
                        }
@@ -470,19 +345,22 @@ static void *collect (void *arg)
 
                                do {
                                        pthread_mutex_lock (&check_mutex);
-                                       type_list_incr (&check, type, 1);
+                                       type_list_incr (&list_check, type, 1);
                                        pthread_mutex_unlock (&check_mutex);
                                } while (NULL != (type = strtok_r (NULL, ",", &ptr)));
                        }
                        else {
-                               log_err ("unknown type '%c'", line[0]);
+                               log_err ("collect: unknown type '%c'", line[0]);
                        }
                } /* while (loop) */
 
-               close (connection->socket);
+               log_debug ("[thread #%5lu] shutting down connection on fd #%i",
+                               pthread_self (), fileno (this->socket));
+
+               fclose (connection->socket);
                free (connection);
 
-               this->socket = -1;
+               this->socket = NULL;
 
                pthread_mutex_lock (&available_mutex);
                ++available_collectors;
@@ -491,7 +369,6 @@ static void *collect (void *arg)
                pthread_cond_signal (&collector_available);
        } /* while (1) */
 
-       free (buffer);
        pthread_exit ((void *)0);
 } /* static void *collect (void *) */
 
@@ -499,6 +376,9 @@ static void *open_connection (void *arg)
 {
        struct sockaddr_un addr;
 
+       char *path  = (NULL == sock_file) ? SOCK_PATH : sock_file;
+       char *group = (NULL == sock_group) ? COLLECTD_GRP_NAME : sock_group;
+
        /* create UNIX socket */
        errno = 0;
        if (-1 == (connector_socket = socket (PF_UNIX, SOCK_STREAM, 0))) {
@@ -511,7 +391,7 @@ static void *open_connection (void *arg)
 
        addr.sun_family = AF_UNIX;
 
-       strncpy (addr.sun_path, sock_file, (size_t)(UNIX_PATH_MAX - 1));
+       strncpy (addr.sun_path, path, (size_t)(UNIX_PATH_MAX - 1));
        addr.sun_path[UNIX_PATH_MAX - 1] = '\0';
 
        errno = 0;
@@ -520,7 +400,8 @@ static void *open_connection (void *arg)
                                        + strlen(addr.sun_path))) {
                char errbuf[1024];
                disabled = 1;
-               connector_socket = -1; /* TODO: close? */
+               close (connector_socket);
+               connector_socket = -1;
                log_err ("bind() failed: %s",
                                sstrerror (errno, errbuf, sizeof (errbuf)));
                pthread_exit ((void *)1);
@@ -530,13 +411,13 @@ static void *open_connection (void *arg)
        if (-1 == listen (connector_socket, 5)) {
                char errbuf[1024];
                disabled = 1;
-               connector_socket = -1; /* TODO: close? */
+               close (connector_socket);
+               connector_socket = -1;
                log_err ("listen() failed: %s",
                                sstrerror (errno, errbuf, sizeof (errbuf)));
                pthread_exit ((void *)1);
        }
 
-       if ((uid_t) 0 == geteuid ())
        {
                struct group sg;
                struct group *grp;
@@ -544,36 +425,32 @@ static void *open_connection (void *arg)
                int status;
 
                grp = NULL;
-               status = getgrnam_r (sock_group, &sg, grbuf, sizeof (grbuf), &grp);
+               status = getgrnam_r (group, &sg, grbuf, sizeof (grbuf), &grp);
                if (status != 0)
                {
                        char errbuf[1024];
-                       log_warn ("getgrnam_r (%s) failed: %s", sock_group,
+                       log_warn ("getgrnam_r (%s) failed: %s", group,
                                        sstrerror (errno, errbuf, sizeof (errbuf)));
                }
                else if (grp == NULL)
                {
-                       log_warn ("No such group: `%s'", sock_group);
+                       log_warn ("No such group: `%s'", group);
                }
                else
                {
-                       status = chown (sock_file, (uid_t) -1, grp->gr_gid);
+                       status = chown (path, (uid_t) -1, grp->gr_gid);
                        if (status != 0)
                        {
                                char errbuf[1024];
                                log_warn ("chown (%s, -1, %i) failed: %s",
-                                               sock_file, (int) grp->gr_gid,
+                                               path, (int) grp->gr_gid,
                                                sstrerror (errno, errbuf, sizeof (errbuf)));
                        }
                }
        }
-       else /* geteuid != 0 */
-       {
-               log_warn ("not running as root");
-       }
 
        errno = 0;
-       if (0 != chmod (sock_file, sock_perms)) {
+       if (0 != chmod (path, sock_perms)) {
                char errbuf[1024];
                log_warn ("chmod() failed: %s",
                                sstrerror (errno, errbuf, sizeof (errbuf)));
@@ -598,7 +475,7 @@ static void *open_connection (void *arg)
 
                for (i = 0; i < max_conns; ++i) {
                        collectors[i] = (collector_t *)smalloc (sizeof (collector_t));
-                       collectors[i]->socket = -1;
+                       collectors[i]->socket = NULL;
 
                        if (0 != (err = pthread_create (&collectors[i]->thread, &ptattr,
                                                        collect, collectors[i]))) {
@@ -633,7 +510,8 @@ static void *open_connection (void *arg)
                                if (EINTR != errno) {
                                        char errbuf[1024];
                                        disabled = 1;
-                                       connector_socket = -1; /* TODO: close? */
+                                       close (connector_socket);
+                                       connector_socket = -1;
                                        log_err ("accept() failed: %s",
                                                        sstrerror (errno, errbuf, sizeof (errbuf)));
                                        pthread_exit ((void *)1);
@@ -643,9 +521,14 @@ static void *open_connection (void *arg)
 
                connection = (conn_t *)smalloc (sizeof (conn_t));
 
-               connection->socket = remote;
+               connection->socket = fdopen (remote, "r");
                connection->next   = NULL;
 
+               if (NULL == connection->socket) {
+                       close (remote);
+                       continue;
+               }
+
                pthread_mutex_lock (&conns_mutex);
 
                if (NULL == conns.head) {
@@ -682,6 +565,8 @@ static int email_init (void)
 
 static int email_shutdown (void)
 {
+       type_t *ptr = NULL;
+
        int i = 0;
 
        if (connector != ((pthread_t) 0)) {
@@ -697,6 +582,8 @@ static int email_shutdown (void)
        /* don't allow any more connections to be processed */
        pthread_mutex_lock (&conns_mutex);
 
+       available_collectors = 0;
+
        if (collectors != NULL) {
                for (i = 0; i < max_conns; ++i) {
                        if (collectors[i] == NULL)
@@ -707,18 +594,52 @@ static int email_shutdown (void)
                                collectors[i]->thread = (pthread_t) 0;
                        }
 
-                       if (collectors[i]->socket >= 0) {
-                               close (collectors[i]->socket);
-                               collectors[i]->socket = -1;
+                       if (collectors[i]->socket != NULL) {
+                               fclose (collectors[i]->socket);
+                               collectors[i]->socket = NULL;
                        }
+
+                       sfree (collectors[i]);
                }
+               sfree (collectors);
        } /* if (collectors != NULL) */
 
        pthread_mutex_unlock (&conns_mutex);
 
-       unlink (sock_file);
-       errno = 0;
+       for (ptr = list_count.head; NULL != ptr; ptr = ptr->next) {
+               free (ptr->name);
+               free (ptr);
+       }
+
+       for (ptr = list_count_copy.head; NULL != ptr; ptr = ptr->next) {
+               free (ptr->name);
+               free (ptr);
+       }
+
+       for (ptr = list_size.head; NULL != ptr; ptr = ptr->next) {
+               free (ptr->name);
+               free (ptr);
+       }
 
+       for (ptr = list_size_copy.head; NULL != ptr; ptr = ptr->next) {
+               free (ptr->name);
+               free (ptr);
+       }
+
+       for (ptr = list_check.head; NULL != ptr; ptr = ptr->next) {
+               free (ptr->name);
+               free (ptr);
+       }
+
+       for (ptr = list_check_copy.head; NULL != ptr; ptr = ptr->next) {
+               free (ptr->name);
+               free (ptr);
+       }
+
+       unlink ((NULL == sock_file) ? SOCK_PATH : sock_file);
+
+       sfree (sock_file);
+       sfree (sock_group);
        return (0);
 } /* static void email_shutdown (void) */
 
@@ -784,47 +705,28 @@ static int email_read (void)
        double score_old;
        int score_count_old;
 
-       static type_list_t *cnt;
-       static type_list_t *sz;
-       static type_list_t *chk;
-
        if (disabled)
                return (-1);
 
-       if (NULL == cnt) {
-               cnt = (type_list_t *)smalloc (sizeof (type_list_t));
-               cnt->head = NULL;
-       }
-
-       if (NULL == sz) {
-               sz = (type_list_t *)smalloc (sizeof (type_list_t));
-               sz->head = NULL;
-       }
-
-       if (NULL == chk) {
-               chk = (type_list_t *)smalloc (sizeof (type_list_t));
-               chk->head = NULL;
-       }
-
        /* email count */
        pthread_mutex_lock (&count_mutex);
 
-       copy_type_list (&count, cnt);
+       copy_type_list (&list_count, &list_count_copy);
 
        pthread_mutex_unlock (&count_mutex);
 
-       for (ptr = cnt->head; NULL != ptr; ptr = ptr->next) {
+       for (ptr = list_count_copy.head; NULL != ptr; ptr = ptr->next) {
                email_submit ("email_count", ptr->name, ptr->value);
        }
 
        /* email size */
        pthread_mutex_lock (&size_mutex);
 
-       copy_type_list (&size, sz);
+       copy_type_list (&list_size, &list_size_copy);
 
        pthread_mutex_unlock (&size_mutex);
 
-       for (ptr = sz->head; NULL != ptr; ptr = ptr->next) {
+       for (ptr = list_size_copy.head; NULL != ptr; ptr = ptr->next) {
                email_submit ("email_size", ptr->name, ptr->value);
        }
 
@@ -844,11 +746,11 @@ static int email_read (void)
        /* spam checks */
        pthread_mutex_lock (&check_mutex);
 
-       copy_type_list (&check, chk);
+       copy_type_list (&list_check, &list_check_copy);
 
        pthread_mutex_unlock (&check_mutex);
 
-       for (ptr = chk->head; NULL != ptr; ptr = ptr->next)
+       for (ptr = list_check_copy.head; NULL != ptr; ptr = ptr->next)
                email_submit ("spam_check", ptr->name, ptr->value);
 
        return (0);
index 5eae906..07c35c9 100644 (file)
@@ -379,14 +379,17 @@ static void exec_child (program_list_t *pl) /* {{{ */
 } /* void exec_child }}} */
 
 /*
- * Creates two pipes (one for reading, ong for writing), forks a child, sets up
- * the pipes so that fd_in is connected to STDIN of the child and fd_out is
- * connected to STDOUT and STDERR of the child. Then is calls `exec_child'.
+ * Creates three pipes (one for reading, one for writing and one for errors),
+ * forks a child, sets up the pipes so that fd_in is connected to STDIN of
+ * the child and fd_out is connected to STDOUT and fd_err is connected to STDERR
+ * of the child. Then is calls `exec_child'.
  */
-static int fork_child (program_list_t *pl, int *fd_in, int *fd_out) /* {{{ */
+static int fork_child (program_list_t *pl, int *fd_in, int *fd_out, int *fd_err) /* {{{ */
 {
   int fd_pipe_in[2];
   int fd_pipe_out[2];
+  int fd_pipe_err[2];
+  char errbuf[1024];
   int status;
   int pid;
 
@@ -396,7 +399,6 @@ static int fork_child (program_list_t *pl, int *fd_in, int *fd_out) /* {{{ */
   status = pipe (fd_pipe_in);
   if (status != 0)
   {
-    char errbuf[1024];
     ERROR ("exec plugin: pipe failed: %s",
        sstrerror (errno, errbuf, sizeof (errbuf)));
     return (-1);
@@ -405,7 +407,14 @@ static int fork_child (program_list_t *pl, int *fd_in, int *fd_out) /* {{{ */
   status = pipe (fd_pipe_out);
   if (status != 0)
   {
-    char errbuf[1024];
+    ERROR ("exec plugin: pipe failed: %s",
+       sstrerror (errno, errbuf, sizeof (errbuf)));
+    return (-1);
+  }
+
+  status = pipe (fd_pipe_err);
+  if (status != 0)
+  {
     ERROR ("exec plugin: pipe failed: %s",
        sstrerror (errno, errbuf, sizeof (errbuf)));
     return (-1);
@@ -414,7 +423,6 @@ static int fork_child (program_list_t *pl, int *fd_in, int *fd_out) /* {{{ */
   pid = fork ();
   if (pid < 0)
   {
-    char errbuf[1024];
     ERROR ("exec plugin: fork failed: %s",
        sstrerror (errno, errbuf, sizeof (errbuf)));
     return (-1);
@@ -428,45 +436,32 @@ static int fork_child (program_list_t *pl, int *fd_in, int *fd_out) /* {{{ */
     fd_num = getdtablesize ();
     for (fd = 0; fd < fd_num; fd++)
     {
-      if ((fd == fd_pipe_in[0]) || (fd == fd_pipe_out[1]))
+      if ((fd == fd_pipe_in[0])
+         || (fd == fd_pipe_out[1])
+         || (fd == fd_pipe_err[1]))
        continue;
       close (fd);
     }
 
-    /* If the `out' pipe has the filedescriptor STDIN we have to be careful
-     * with the `dup's below. So, if this is the case we have to handle the
-     * `out' pipe first. */
-    if (fd_pipe_out[1] == STDIN_FILENO)
-    {
-      int new_fileno = (fd_pipe_in[0] == STDOUT_FILENO)
-       ? STDERR_FILENO : STDOUT_FILENO;
-      dup2 (fd_pipe_out[1], new_fileno);
-      close (fd_pipe_out[1]);
-      fd_pipe_out[1] = new_fileno;
-    }
-    /* Now `fd_pipe_out[1]' is either `STDOUT' or `STDERR', but definitely not
-     * `STDIN_FILENO'. */
-
     /* Connect the `in' pipe to STDIN */
     if (fd_pipe_in[0] != STDIN_FILENO)
     {
       dup2 (fd_pipe_in[0], STDIN_FILENO);
       close (fd_pipe_in[0]);
-      fd_pipe_in[0] = STDIN_FILENO;
     }
 
-    /* Now connect the `out' pipe to STDOUT and STDERR */
+    /* Now connect the `out' pipe to STDOUT */
     if (fd_pipe_out[1] != STDOUT_FILENO)
+    {
       dup2 (fd_pipe_out[1], STDOUT_FILENO);
-    if (fd_pipe_out[1] != STDERR_FILENO)
-      dup2 (fd_pipe_out[1], STDERR_FILENO);
+      close (fd_pipe_out[1]);
+    }
 
-    /* If the pipe has some FD that's something completely different, close it
-     * now. */
-    if ((fd_pipe_out[1] != STDOUT_FILENO) && (fd_pipe_out[1] != STDERR_FILENO))
+    /* Now connect the `out' pipe to STDOUT */
+    if (fd_pipe_err[1] != STDERR_FILENO)
     {
-      close (fd_pipe_out[1]);
-      fd_pipe_out[1] = STDOUT_FILENO;
+      dup2 (fd_pipe_err[1], STDERR_FILENO);
+      close (fd_pipe_err[1]);
     }
 
     exec_child (pl);
@@ -475,6 +470,7 @@ static int fork_child (program_list_t *pl, int *fd_in, int *fd_out) /* {{{ */
 
   close (fd_pipe_in[0]);
   close (fd_pipe_out[1]);
+  close (fd_pipe_err[1]);
 
   if (fd_in != NULL)
     *fd_in = fd_pipe_in[1];
@@ -486,6 +482,11 @@ static int fork_child (program_list_t *pl, int *fd_in, int *fd_out) /* {{{ */
   else
     close (fd_pipe_out[0]);
 
+  if (fd_err != NULL)
+    *fd_err = fd_pipe_err[0];
+  else
+    close (fd_pipe_err[0]);
+
   return (pid);
 } /* int fork_child }}} */
 
@@ -509,48 +510,112 @@ static int parse_line (char *buffer) /* {{{ */
 static void *exec_read_one (void *arg) /* {{{ */
 {
   program_list_t *pl = (program_list_t *) arg;
-  int fd;
-  FILE *fh;
-  char buffer[1024];
+  int fd, fd_err, highest_fd;
+  fd_set fdset, copy;
   int status;
+  char buffer[1200];  /* if not completely read */
+  char buffer_err[1024];
+  char *pbuffer = buffer;
+  char *pbuffer_err = buffer_err;
 
-  status = fork_child (pl, NULL, &fd);
+  status = fork_child (pl, NULL, &fd, &fd_err);
   if (status < 0)
     pthread_exit ((void *) 1);
   pl->pid = status;
 
   assert (pl->pid != 0);
 
-  fh = fdopen (fd, "r");
-  if (fh == NULL)
-  {
-    char errbuf[1024];
-    ERROR ("exec plugin: fdopen (%i) failed: %s", fd,
-       sstrerror (errno, errbuf, sizeof (errbuf)));
-    kill (pl->pid, SIGTERM);
-    pl->pid = 0;
-    close (fd);
-    pthread_exit ((void *) 1);
-  }
+  FD_ZERO( &fdset );
+  FD_SET(fd, &fdset);
+  FD_SET(fd_err, &fdset);
 
-  buffer[0] = '\0';
-  while (fgets (buffer, sizeof (buffer), fh) != NULL)
+  /* Determine the highest file descriptor */
+  highest_fd = (fd > fd_err) ? fd : fd_err;
+
+  /* We use a copy of fdset, as select modifies it */
+  copy = fdset;
+
+  while (select(highest_fd + 1, &copy, NULL, NULL, NULL ) > 0)
   {
     int len;
 
-    len = strlen (buffer);
+    if (FD_ISSET(fd, &copy))
+    {
+      char *pnl;
 
-    /* Remove newline from end. */
-    while ((len > 0) && ((buffer[len - 1] == '\n')
-         || (buffer[len - 1] == '\r')))
-      buffer[--len] = '\0';
+      len = read(fd, pbuffer, sizeof(buffer) - 1 - (pbuffer - buffer));
 
-    DEBUG ("exec plugin: exec_read_one: buffer = %s", buffer);
+      if (len < 0)
+      {
+        if (errno == EAGAIN || errno == EINTR)  continue;
+        break;
+      }
+      else if (len == 0) break;  /* We've reached EOF */
 
-    parse_line (buffer);
-  } /* while (fgets) */
+      pbuffer[len] = '\0';
 
-  fclose (fh);
+      len += pbuffer - buffer;
+      pbuffer = buffer;
+
+      while ((pnl = strchr(pbuffer, '\n')))
+      {
+        *pnl = '\0';
+        if (*(pnl-1) == '\r' ) *(pnl-1) = '\0';
+
+        parse_line (pbuffer);
+
+        pbuffer = ++pnl;
+      }
+      /* not completely read ? */
+      if (pbuffer - buffer < len)
+      {
+        len -= pbuffer - buffer;
+        memmove(buffer, pbuffer, len);
+        pbuffer = buffer + len;
+      }
+      else
+        pbuffer = buffer;
+    }
+    else if (FD_ISSET(fd_err, &copy))
+    {
+      char *pnl;
+
+      len = read(fd_err, pbuffer_err, sizeof(buffer_err) - 1 - (pbuffer_err - buffer_err));
+
+      if (len < 0)
+      {
+        if (errno == EAGAIN || errno == EINTR)  continue;
+        break;
+      }
+      else if (len == 0) break;  /* We've reached EOF */
+
+      pbuffer_err[len] = '\0';
+
+      len += pbuffer_err - buffer_err;
+      pbuffer_err = buffer_err;
+
+      while ((pnl = strchr(pbuffer_err, '\n')))
+      {
+        *pnl = '\0';
+        if (*(pnl-1) == '\r' ) *(pnl-1) = '\0';
+
+        ERROR ("exec plugin: exec_read_one: error = %s", pbuffer_err);
+
+        pbuffer_err = ++pnl;
+      }
+      /* not completely read ? */
+      if (pbuffer_err - buffer_err < len)
+      {
+        len -= pbuffer_err - buffer_err;
+        memmove(buffer_err, pbuffer_err, len);
+        pbuffer_err = buffer_err + len;
+      }
+      else
+        pbuffer_err = buffer_err;
+    }
+    /* reset copy */
+    copy = fdset;
+  }
 
   if (waitpid (pl->pid, &status, 0) > 0)
     pl->status = status;
@@ -564,6 +629,9 @@ static void *exec_read_one (void *arg) /* {{{ */
   pl->flags &= ~PL_RUNNING;
   pthread_mutex_unlock (&pl_lock);
 
+  close (fd);
+  close (fd_err);
+
   pthread_exit ((void *) 0);
   return (NULL);
 } /* void *exec_read_one }}} */
@@ -578,7 +646,7 @@ static void *exec_notification_one (void *arg) /* {{{ */
   int status;
   const char *severity;
 
-  pid = fork_child (pl, &fd, NULL);
+  pid = fork_child (pl, &fd, NULL, NULL);
   if (pid < 0) {
     sfree (arg);
     pthread_exit ((void *) 1);
diff --git a/src/ipmi.c b/src/ipmi.c
new file mode 100644 (file)
index 0000000..956aaf4
--- /dev/null
@@ -0,0 +1,554 @@
+/**
+ * collectd - src/ipmi.c
+ * Copyright (C) 2008  Florian octo Forster
+ *
+ * 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; only version 2 of the License is applicable.
+ *
+ * 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 St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "utils_ignorelist.h"
+
+#include <pthread.h>
+
+#include <OpenIPMI/ipmiif.h>
+#include <OpenIPMI/ipmi_err.h>
+#include <OpenIPMI/ipmi_posix.h>
+#include <OpenIPMI/ipmi_conn.h>
+#include <OpenIPMI/ipmi_smi.h>
+
+/*
+ * Private data types
+ */
+struct c_ipmi_sensor_list_s;
+typedef struct c_ipmi_sensor_list_s c_ipmi_sensor_list_t;
+
+struct c_ipmi_sensor_list_s
+{
+  ipmi_sensor_id_t sensor_id;
+  c_ipmi_sensor_list_t *next;
+};
+
+/*
+ * Module global variables
+ */
+static pthread_mutex_t sensor_list_lock = PTHREAD_MUTEX_INITIALIZER;
+static c_ipmi_sensor_list_t *sensor_list = NULL;
+
+static int c_ipmi_active = 0;
+static pthread_t thread_id = (pthread_t) 0;
+
+static const char *config_keys[] =
+{
+       "Sensor",
+       "IgnoreSelected"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+
+static ignorelist_t *ignorelist = NULL;
+
+/*
+ * Misc private functions
+ */
+static void c_ipmi_error (const char *func, int status)
+{
+  char errbuf[4096];
+
+  memset (errbuf, 0, sizeof (errbuf));
+
+  if (IPMI_IS_OS_ERR (status))
+  {
+    sstrerror (IPMI_GET_OS_ERR (status), errbuf, sizeof (errbuf));
+  }
+  else if (IPMI_IS_IPMI_ERR (status))
+  {
+    ipmi_get_error_string (IPMI_GET_IPMI_ERR (status), errbuf, sizeof (errbuf));
+  }
+
+  if (errbuf[0] == 0)
+  {
+    ssnprintf (errbuf, sizeof (errbuf), "Unknown error %#x", status);
+  }
+  errbuf[sizeof (errbuf) - 1] = 0;
+
+  ERROR ("ipmi plugin: %s failed: %s", func, errbuf);
+} /* void c_ipmi_error */
+
+/*
+ * Sensor handlers
+ */
+/* Prototype for sensor_list_remove, so sensor_read_handler can call it. */
+static int sensor_list_remove (ipmi_sensor_t *sensor);
+
+static void sensor_read_handler (ipmi_sensor_t *sensor,
+    int err,
+    enum ipmi_value_present_e value_present,
+    unsigned int raw_value,
+    double value,
+    ipmi_states_t *states,
+    void *user_data)
+{
+  value_t values[1];
+  value_list_t vl = VALUE_LIST_INIT;
+
+  char sensor_name[IPMI_SENSOR_NAME_LEN];
+  char *sensor_name_ptr;
+  int sensor_type;
+  const char *type;
+
+  memset (sensor_name, 0, sizeof (sensor_name));
+  ipmi_sensor_get_name (sensor, sensor_name, sizeof (sensor_name));
+  sensor_name[sizeof (sensor_name) - 1] = 0;
+
+  sensor_name_ptr = strstr (sensor_name, ").");
+  if (sensor_name_ptr == NULL)
+    sensor_name_ptr = sensor_name;
+  else
+    sensor_name_ptr += 2;
+
+  if (err != 0)
+  {
+    INFO ("ipmi plugin: sensor_read_handler: Removing sensor %s, "
+        "because it failed with status %#x.",
+        sensor_name_ptr, err);
+    sensor_list_remove (sensor);
+    return;
+  }
+
+  if (value_present != IPMI_BOTH_VALUES_PRESENT)
+  {
+    INFO ("ipmi plugin: sensor_read_handler: Removing sensor %s, "
+        "because it provides %s. If you need this sensor, "
+        "please file a bug report.",
+        sensor_name_ptr,
+        (value_present == IPMI_RAW_VALUE_PRESENT)
+        ? "only the raw value"
+        : "no value");
+    sensor_list_remove (sensor);
+    return;
+  }
+
+  /* Both `ignorelist' and `plugin_instance' may be NULL. */
+  if (ignorelist_match (ignorelist, sensor_name_ptr) != 0)
+  {
+    sensor_list_remove (sensor);
+    return;
+  }
+
+  /* FIXME: Use rate unit or base unit to scale the value */
+
+  sensor_type = ipmi_sensor_get_sensor_type (sensor);
+  switch (sensor_type)
+  {
+    case IPMI_SENSOR_TYPE_TEMPERATURE:
+      type = "temperature";
+      break;
+
+    case IPMI_SENSOR_TYPE_VOLTAGE:
+      type = "voltage";
+      break;
+
+    case IPMI_SENSOR_TYPE_CURRENT:
+      type = "current";
+      break;
+
+    case IPMI_SENSOR_TYPE_FAN:
+      type = "fanspeed";
+      break;
+
+    default:
+      {
+        const char *sensor_type_str;
+        
+        sensor_type_str = ipmi_sensor_get_sensor_type_string (sensor);
+        INFO ("ipmi plugin: sensor_read_handler: Removing sensor %s, "
+            "because I don't know how to handle its type (%#x, %s). "
+            "If you need this sensor, please file a bug report.",
+            sensor_name_ptr, sensor_type, sensor_type_str);
+        sensor_list_remove (sensor);
+        return;
+      }
+  } /* switch (sensor_type) */
+
+  values[0].gauge = value;
+
+  vl.values = values;
+  vl.values_len = 1;
+  vl.time = time (NULL);
+
+  sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+  sstrncpy (vl.plugin, "ipmi", sizeof (vl.plugin));
+  sstrncpy (vl.type_instance, sensor_name_ptr, sizeof (vl.type_instance));
+
+  plugin_dispatch_values (type, &vl);
+} /* void sensor_read_handler */
+
+static int sensor_list_add (ipmi_sensor_t *sensor)
+{
+  ipmi_sensor_id_t sensor_id;
+  c_ipmi_sensor_list_t *list_item;
+  c_ipmi_sensor_list_t *list_prev;
+
+  sensor_id = ipmi_sensor_convert_to_id (sensor);
+
+  pthread_mutex_lock (&sensor_list_lock);
+
+  list_prev = NULL;
+  for (list_item = sensor_list;
+      list_item != NULL;
+      list_item = list_item->next)
+  {
+    if (ipmi_cmp_sensor_id (sensor_id, list_item->sensor_id) == 0)
+      break;
+    list_prev = list_item;
+  } /* for (list_item) */
+
+  if (list_item != NULL)
+  {
+    pthread_mutex_unlock (&sensor_list_lock);
+    return (0);
+  }
+
+  list_item = (c_ipmi_sensor_list_t *) calloc (1, sizeof (c_ipmi_sensor_list_t));
+  if (list_item == NULL)
+  {
+    pthread_mutex_unlock (&sensor_list_lock);
+    return (-1);
+  }
+
+  list_item->sensor_id = ipmi_sensor_convert_to_id (sensor);
+
+  if (list_prev != NULL)
+    list_prev->next = list_item;
+  else
+    sensor_list = list_item;
+
+  pthread_mutex_unlock (&sensor_list_lock);
+
+  return (0);
+} /* int sensor_list_add */
+
+static int sensor_list_remove (ipmi_sensor_t *sensor)
+{
+  ipmi_sensor_id_t sensor_id;
+  c_ipmi_sensor_list_t *list_item;
+  c_ipmi_sensor_list_t *list_prev;
+
+  sensor_id = ipmi_sensor_convert_to_id (sensor);
+
+  pthread_mutex_lock (&sensor_list_lock);
+
+  list_prev = NULL;
+  for (list_item = sensor_list;
+      list_item != NULL;
+      list_item = list_item->next)
+  {
+    if (ipmi_cmp_sensor_id (sensor_id, list_item->sensor_id) == 0)
+      break;
+    list_prev = list_item;
+  } /* for (list_item) */
+
+  if (list_item == NULL)
+  {
+    pthread_mutex_unlock (&sensor_list_lock);
+    return (-1);
+  }
+
+  if (list_prev == NULL)
+    sensor_list = list_item->next;
+  else
+    list_prev->next = list_item->next;
+
+  list_prev = NULL;
+  list_item->next = NULL;
+
+  pthread_mutex_unlock (&sensor_list_lock);
+
+  free (list_item);
+  return (0);
+} /* int sensor_list_remove */
+
+static int sensor_list_read_all (void)
+{
+  c_ipmi_sensor_list_t *list_item;
+
+  pthread_mutex_lock (&sensor_list_lock);
+
+  for (list_item = sensor_list;
+      list_item != NULL;
+      list_item = list_item->next)
+  {
+    ipmi_sensor_id_get_reading (list_item->sensor_id,
+        sensor_read_handler, /* user data = */ NULL);
+  } /* for (list_item) */
+
+  pthread_mutex_unlock (&sensor_list_lock);
+
+  return (0);
+} /* int sensor_list_read_all */
+
+static int sensor_list_remove_all (void)
+{
+  c_ipmi_sensor_list_t *list_item;
+
+  pthread_mutex_lock (&sensor_list_lock);
+
+  list_item = sensor_list;
+  sensor_list = NULL;
+
+  pthread_mutex_unlock (&sensor_list_lock);
+
+  while (list_item != NULL)
+  {
+    c_ipmi_sensor_list_t *list_next = list_item->next;
+
+    free (list_item);
+
+    list_item = list_next;
+  } /* while (list_item) */
+
+  return (0);
+} /* int sensor_list_remove_all */
+
+/*
+ * Entity handlers
+ */
+static void entity_sensor_update_handler (enum ipmi_update_e op,
+    ipmi_entity_t *entity,
+    ipmi_sensor_t *sensor,
+    void *user_data)
+{
+  /* TODO: Ignore sensors we cannot read */
+
+  if ((op == IPMI_ADDED) || (op == IPMI_CHANGED))
+  {
+    /* Will check for duplicate entries.. */
+    sensor_list_add (sensor);
+  }
+  else if (op == IPMI_DELETED)
+  {
+    sensor_list_remove (sensor);
+  }
+} /* void entity_sensor_update_handler */
+
+/*
+ * Domain handlers
+ */
+static void domain_entity_update_handler (enum ipmi_update_e op,
+    ipmi_domain_t *domain,
+    ipmi_entity_t *entity,
+    void *user_data)
+{
+  int status;
+
+  if (op == IPMI_ADDED)
+  {
+    status = ipmi_entity_add_sensor_update_handler (entity,
+        entity_sensor_update_handler, /* user data = */ NULL);
+    if (status != 0)
+    {
+      c_ipmi_error ("ipmi_entity_add_sensor_update_handler", status);
+    }
+  }
+  else if (op == IPMI_DELETED)
+  {
+    status = ipmi_entity_remove_sensor_update_handler (entity,
+        entity_sensor_update_handler, /* user data = */ NULL);
+    if (status != 0)
+    {
+      c_ipmi_error ("ipmi_entity_remove_sensor_update_handler", status);
+    }
+  }
+} /* void domain_entity_update_handler */
+
+static void domain_connection_change_handler (ipmi_domain_t *domain,
+    int err,
+    unsigned int conn_num,
+    unsigned int port_num,
+    int still_connected,
+    void *user_data)
+{
+  int status;
+
+  printf ("domain_connection_change_handler (domain = %p, err = %i, "
+      "conn_num = %u, port_num = %u, still_connected = %i, "
+      "user_data = %p);\n",
+      (void *) domain, err, conn_num, port_num, still_connected, user_data);
+
+  status = ipmi_domain_add_entity_update_handler (domain,
+      domain_entity_update_handler, /* user data = */ NULL);
+  if (status != 0)
+  {
+    c_ipmi_error ("ipmi_domain_add_entity_update_handler", status);
+  }
+} /* void domain_connection_change_handler */
+
+static int thread_init (os_handler_t **ret_os_handler)
+{
+  os_handler_t *os_handler;
+  ipmi_open_option_t open_option[1];
+  ipmi_con_t *smi_connection = NULL;
+  ipmi_domain_id_t domain_id;
+  int status;
+
+  os_handler = ipmi_posix_thread_setup_os_handler (SIGUSR2);
+  if (os_handler == NULL)
+  {
+    ERROR ("ipmi plugin: ipmi_posix_thread_setup_os_handler failed.");
+    return (-1);
+  }
+
+  ipmi_init (os_handler);
+
+  status = ipmi_smi_setup_con (/* if_num = */ 0,
+      os_handler,
+      /* user data = */ NULL,
+      &smi_connection);
+  if (status != 0)
+  {
+    c_ipmi_error ("ipmi_smi_setup_con", status);
+    return (-1);
+  }
+
+  memset (open_option, 0, sizeof (open_option));
+  open_option[0].option = IPMI_OPEN_OPTION_ALL;
+  open_option[0].ival = 1;
+
+  status = ipmi_open_domain ("mydomain", &smi_connection, /* num_con = */ 1,
+      domain_connection_change_handler, /* user data = */ NULL,
+      /* domain_fully_up_handler = */ NULL, /* user data = */ NULL,
+      open_option, sizeof (open_option) / sizeof (open_option[0]),
+      &domain_id);
+  if (status != 0)
+  {
+    c_ipmi_error ("ipmi_open_domain", status);
+    return (-1);
+  }
+
+  *ret_os_handler = os_handler;
+  return (0);
+} /* int thread_init */
+
+static void *thread_main (void *user_data)
+{
+  int status;
+  os_handler_t *os_handler = NULL;
+
+  status = thread_init (&os_handler);
+  if (status != 0)
+  {
+    fprintf (stderr, "ipmi plugin: thread_init failed.\n");
+    return ((void *) -1);
+  }
+
+  while (c_ipmi_active != 0)
+  {
+    struct timeval tv = { 1, 0 };
+    os_handler->perform_one_op (os_handler, &tv);
+  }
+
+  ipmi_posix_thread_free_os_handler (os_handler);
+
+  return ((void *) 0);
+} /* void *thread_main */
+
+static int c_ipmi_config (const char *key, const char *value)
+{
+  if (ignorelist == NULL)
+    ignorelist = ignorelist_create (/* invert = */ 1);
+  if (ignorelist == NULL)
+    return (1);
+
+  if (strcasecmp ("Sensor", key) == 0)
+  {
+    ignorelist_add (ignorelist, value);
+  }
+  else if (strcasecmp ("IgnoreSelected", key) == 0)
+  {
+    int invert = 1;
+    if ((strcasecmp ("True", value) == 0)
+       || (strcasecmp ("Yes", value) == 0)
+       || (strcasecmp ("On", value) == 0))
+      invert = 0;
+    ignorelist_set_invert (ignorelist, invert);
+  }
+  else
+  {
+    return (-1);
+  }
+
+  return (0);
+} /* int c_ipmi_config */
+
+static int c_ipmi_init (void)
+{
+  int status;
+
+  c_ipmi_active = 1;
+
+  status = pthread_create (&thread_id, /* attr = */ NULL, thread_main,
+      /* user data = */ NULL);
+  if (status != 0)
+  {
+    c_ipmi_active = 0;
+    thread_id = (pthread_t) 0;
+    ERROR ("ipmi plugin: pthread_create failed.");
+    return (-1);
+  }
+
+  return (0);
+} /* int c_ipmi_init */
+
+static int c_ipmi_read (void)
+{
+  if ((c_ipmi_active == 0) || (thread_id == (pthread_t) 0))
+  {
+    INFO ("ipmi plugin: c_ipmi_read: I'm not active, returning false.");
+    return (-1);
+  }
+
+  sensor_list_read_all ();
+  
+  return (0);
+} /* int c_ipmi_read */
+
+static int c_ipmi_shutdown (void)
+{
+  c_ipmi_active = 0;
+
+  if (thread_id != (pthread_t) 0)
+  {
+    pthread_join (thread_id, NULL);
+    thread_id = (pthread_t) 0;
+  }
+
+  sensor_list_remove_all ();
+
+  return (0);
+} /* int c_ipmi_shutdown */
+
+void module_register (void)
+{
+  plugin_register_config ("ipmi", c_ipmi_config,
+      config_keys, config_keys_num);
+  plugin_register_init ("ipmi", c_ipmi_init);
+  plugin_register_read ("ipmi", c_ipmi_read);
+  plugin_register_shutdown ("ipmi", c_ipmi_shutdown);
+} /* void module_register */
+
+/* vim: set sw=2 sts=2 ts=8 fdm=marker et : */
index 6522a55..ea8c65e 100644 (file)
@@ -25,7 +25,9 @@
 #include "plugin.h"
 #include "configfile.h"
 
-#if HAVE_LIBIPTC_LIBIPTC_H
+#if OWN_LIBIPTC
+# include "libiptc/libiptc.h"
+#else
 # include <libiptc/libiptc.h>
 #endif
 
diff --git a/src/libiptc/Makefile.am b/src/libiptc/Makefile.am
new file mode 100644 (file)
index 0000000..338aecb
--- /dev/null
@@ -0,0 +1,14 @@
+AUTOMAKE_OPTIONS = foreign no-dependencies
+
+EXTRA_DIST = libiptc.c
+
+if COMPILER_IS_GCC
+AM_CFLAGS = -Wall -Werror
+endif
+
+noinst_LTLIBRARIES = libiptc.la
+
+libiptc_la_CFLAGS = -DIPTABLES_VERSION=\"1.4.0\" -I$(KERNEL_DIR)/include
+libiptc_la_SOURCES = libip4tc.c libip6tc.c \
+               ipt_kernel_headers.h libip6tc.h libiptc.h linux_list.h
+
diff --git a/src/libiptc/ipt_kernel_headers.h b/src/libiptc/ipt_kernel_headers.h
new file mode 100644 (file)
index 0000000..bf81f6e
--- /dev/null
@@ -0,0 +1,45 @@
+/**
+ * This file was imported from the iptables sources.
+ * Copyright (C) 1999-2008 Netfilter Core Team
+ *
+ * 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; only version 2 of the License is applicable.
+ *
+ * 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 St, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+/* This is the userspace/kernel interface for Generic IP Chains,
+   required for libc6. */
+#ifndef _FWCHAINS_KERNEL_HEADERS_H
+#define _FWCHAINS_KERNEL_HEADERS_H
+
+#include <limits.h>
+
+#if defined(__GLIBC__) && __GLIBC__ == 2
+#include <netinet/ip.h>
+#include <netinet/in.h>
+#include <netinet/ip_icmp.h>
+#include <netinet/tcp.h>
+#include <netinet/udp.h>
+#include <net/if.h>
+#include <sys/types.h>
+#else /* libc5 */
+#include <sys/socket.h>
+#include <linux/ip.h>
+#include <linux/in.h>
+#include <linux/if.h>
+#include <linux/icmp.h>
+#include <linux/tcp.h>
+#include <linux/udp.h>
+#include <linux/types.h>
+#include <linux/in6.h>
+#endif
+#endif
diff --git a/src/libiptc/libip4tc.c b/src/libiptc/libip4tc.c
new file mode 100644 (file)
index 0000000..66abb44
--- /dev/null
@@ -0,0 +1,516 @@
+/**
+ * This file was imported from the iptables sources.
+ * Copyright (C) 1999-2008 Netfilter Core Team
+ *
+ * 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; only version 2 of the License is applicable.
+ *
+ * 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 St, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+/* Library which manipulates firewall rules.  Version 0.1. */
+
+/* Architecture of firewall rules is as follows:
+ *
+ * Chains go INPUT, FORWARD, OUTPUT then user chains.
+ * Each user chain starts with an ERROR node.
+ * Every chain ends with an unconditional jump: a RETURN for user chains,
+ * and a POLICY for built-ins.
+ */
+
+/* (C)1999 Paul ``Rusty'' Russell - Placed under the GNU GPL (See
+   COPYING for details). */
+
+#include <assert.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#ifdef DEBUG_CONNTRACK
+#define inline
+#endif
+
+#if !defined(__GLIBC__) || (__GLIBC__ < 2)
+typedef unsigned int socklen_t;
+#endif
+
+#include "libiptc.h"
+
+#define IP_VERSION     4
+#define IP_OFFSET      0x1FFF
+
+#define HOOK_PRE_ROUTING       NF_IP_PRE_ROUTING
+#define HOOK_LOCAL_IN          NF_IP_LOCAL_IN
+#define HOOK_FORWARD           NF_IP_FORWARD
+#define HOOK_LOCAL_OUT         NF_IP_LOCAL_OUT
+#define HOOK_POST_ROUTING      NF_IP_POST_ROUTING
+#ifdef NF_IP_DROPPING
+#define HOOK_DROPPING          NF_IP_DROPPING
+#endif
+
+#define STRUCT_ENTRY_TARGET    struct ipt_entry_target
+#define STRUCT_ENTRY           struct ipt_entry
+#define STRUCT_ENTRY_MATCH     struct ipt_entry_match
+#define STRUCT_GETINFO         struct ipt_getinfo
+#define STRUCT_GET_ENTRIES     struct ipt_get_entries
+#define STRUCT_COUNTERS                struct ipt_counters
+#define STRUCT_COUNTERS_INFO   struct ipt_counters_info
+#define STRUCT_STANDARD_TARGET struct ipt_standard_target
+#define STRUCT_REPLACE         struct ipt_replace
+
+#define STRUCT_TC_HANDLE       struct iptc_handle
+#define TC_HANDLE_T            iptc_handle_t
+
+#define ENTRY_ITERATE          IPT_ENTRY_ITERATE
+#define TABLE_MAXNAMELEN       IPT_TABLE_MAXNAMELEN
+#define FUNCTION_MAXNAMELEN    IPT_FUNCTION_MAXNAMELEN
+
+#define GET_TARGET             ipt_get_target
+
+#define ERROR_TARGET           IPT_ERROR_TARGET
+#define NUMHOOKS               NF_IP_NUMHOOKS
+
+#define IPT_CHAINLABEL         ipt_chainlabel
+
+#define TC_DUMP_ENTRIES                dump_entries
+#define TC_IS_CHAIN            iptc_is_chain
+#define TC_FIRST_CHAIN         iptc_first_chain
+#define TC_NEXT_CHAIN          iptc_next_chain
+#define TC_FIRST_RULE          iptc_first_rule
+#define TC_NEXT_RULE           iptc_next_rule
+#define TC_GET_TARGET          iptc_get_target
+#define TC_BUILTIN             iptc_builtin
+#define TC_GET_POLICY          iptc_get_policy
+#define TC_INSERT_ENTRY                iptc_insert_entry
+#define TC_REPLACE_ENTRY       iptc_replace_entry
+#define TC_APPEND_ENTRY                iptc_append_entry
+#define TC_DELETE_ENTRY                iptc_delete_entry
+#define TC_DELETE_NUM_ENTRY    iptc_delete_num_entry
+#define TC_CHECK_PACKET                iptc_check_packet
+#define TC_FLUSH_ENTRIES       iptc_flush_entries
+#define TC_ZERO_ENTRIES                iptc_zero_entries
+#define TC_READ_COUNTER                iptc_read_counter
+#define TC_ZERO_COUNTER                iptc_zero_counter
+#define TC_SET_COUNTER         iptc_set_counter
+#define TC_CREATE_CHAIN                iptc_create_chain
+#define TC_GET_REFERENCES      iptc_get_references
+#define TC_DELETE_CHAIN                iptc_delete_chain
+#define TC_RENAME_CHAIN                iptc_rename_chain
+#define TC_SET_POLICY          iptc_set_policy
+#define TC_GET_RAW_SOCKET      iptc_get_raw_socket
+#define TC_INIT                        iptc_init
+#define TC_FREE                        iptc_free
+#define TC_COMMIT              iptc_commit
+#define TC_STRERROR            iptc_strerror
+#define TC_NUM_RULES           iptc_num_rules
+#define TC_GET_RULE            iptc_get_rule
+
+#define TC_AF                  AF_INET
+#define TC_IPPROTO             IPPROTO_IP
+
+#define SO_SET_REPLACE         IPT_SO_SET_REPLACE
+#define SO_SET_ADD_COUNTERS    IPT_SO_SET_ADD_COUNTERS
+#define SO_GET_INFO            IPT_SO_GET_INFO
+#define SO_GET_ENTRIES         IPT_SO_GET_ENTRIES
+#define SO_GET_VERSION         IPT_SO_GET_VERSION
+
+#define STANDARD_TARGET                IPT_STANDARD_TARGET
+#define LABEL_RETURN           IPTC_LABEL_RETURN
+#define LABEL_ACCEPT           IPTC_LABEL_ACCEPT
+#define LABEL_DROP             IPTC_LABEL_DROP
+#define LABEL_QUEUE            IPTC_LABEL_QUEUE
+
+#define ALIGN                  IPT_ALIGN
+#define RETURN                 IPT_RETURN
+
+#include "libiptc.c"
+
+#define IP_PARTS_NATIVE(n)                     \
+(unsigned int)((n)>>24)&0xFF,                  \
+(unsigned int)((n)>>16)&0xFF,                  \
+(unsigned int)((n)>>8)&0xFF,                   \
+(unsigned int)((n)&0xFF)
+
+#define IP_PARTS(n) IP_PARTS_NATIVE(ntohl(n))
+
+int
+dump_entry(STRUCT_ENTRY *e, const TC_HANDLE_T handle)
+{
+       size_t i;
+       STRUCT_ENTRY_TARGET *t;
+
+       printf("Entry %u (%lu):\n", iptcb_entry2index(handle, e),
+              iptcb_entry2offset(handle, e));
+       printf("SRC IP: %u.%u.%u.%u/%u.%u.%u.%u\n",
+              IP_PARTS(e->ip.src.s_addr),IP_PARTS(e->ip.smsk.s_addr));
+       printf("DST IP: %u.%u.%u.%u/%u.%u.%u.%u\n",
+              IP_PARTS(e->ip.dst.s_addr),IP_PARTS(e->ip.dmsk.s_addr));
+       printf("Interface: `%s'/", e->ip.iniface);
+       for (i = 0; i < IFNAMSIZ; i++)
+               printf("%c", e->ip.iniface_mask[i] ? 'X' : '.');
+       printf("to `%s'/", e->ip.outiface);
+       for (i = 0; i < IFNAMSIZ; i++)
+               printf("%c", e->ip.outiface_mask[i] ? 'X' : '.');
+       printf("\nProtocol: %u\n", e->ip.proto);
+       printf("Flags: %02X\n", e->ip.flags);
+       printf("Invflags: %02X\n", e->ip.invflags);
+       printf("Counters: %llu packets, %llu bytes\n",
+              (unsigned long long)e->counters.pcnt, (unsigned long long)e->counters.bcnt);
+       printf("Cache: %08X\n", e->nfcache);
+
+       IPT_MATCH_ITERATE(e, print_match);
+
+       t = GET_TARGET(e);
+       printf("Target name: `%s' [%u]\n", t->u.user.name, t->u.target_size);
+       if (strcmp(t->u.user.name, STANDARD_TARGET) == 0) {
+               int pos = *(int *)t->data;
+               if (pos < 0)
+                       printf("verdict=%s\n",
+                              pos == -NF_ACCEPT-1 ? "NF_ACCEPT"
+                              : pos == -NF_DROP-1 ? "NF_DROP"
+                              : pos == -NF_QUEUE-1 ? "NF_QUEUE"
+                              : pos == RETURN ? "RETURN"
+                              : "UNKNOWN");
+               else
+                       printf("verdict=%u\n", pos);
+       } else if (strcmp(t->u.user.name, IPT_ERROR_TARGET) == 0)
+               printf("error=`%s'\n", t->data);
+
+       printf("\n");
+       return 0;
+}
+
+static unsigned char *
+is_same(const STRUCT_ENTRY *a, const STRUCT_ENTRY *b, unsigned char *matchmask)
+{
+       unsigned int i;
+       unsigned char *mptr;
+
+       /* Always compare head structures: ignore mask here. */
+       if (a->ip.src.s_addr != b->ip.src.s_addr
+           || a->ip.dst.s_addr != b->ip.dst.s_addr
+           || a->ip.smsk.s_addr != b->ip.smsk.s_addr
+           || a->ip.dmsk.s_addr != b->ip.dmsk.s_addr
+           || a->ip.proto != b->ip.proto
+           || a->ip.flags != b->ip.flags
+           || a->ip.invflags != b->ip.invflags)
+               return NULL;
+
+       for (i = 0; i < IFNAMSIZ; i++) {
+               if (a->ip.iniface_mask[i] != b->ip.iniface_mask[i])
+                       return NULL;
+               if ((a->ip.iniface[i] & a->ip.iniface_mask[i])
+                   != (b->ip.iniface[i] & b->ip.iniface_mask[i]))
+                       return NULL;
+               if (a->ip.outiface_mask[i] != b->ip.outiface_mask[i])
+                       return NULL;
+               if ((a->ip.outiface[i] & a->ip.outiface_mask[i])
+                   != (b->ip.outiface[i] & b->ip.outiface_mask[i]))
+                       return NULL;
+       }
+
+       if (a->target_offset != b->target_offset
+           || a->next_offset != b->next_offset)
+               return NULL;
+
+       mptr = matchmask + sizeof(STRUCT_ENTRY);
+       if (IPT_MATCH_ITERATE(a, match_different, a->elems, b->elems, &mptr))
+               return NULL;
+       mptr += IPT_ALIGN(sizeof(struct ipt_entry_target));
+
+       return mptr;
+}
+
+#if 0
+/***************************** DEBUGGING ********************************/
+static inline int
+unconditional(const struct ipt_ip *ip)
+{
+       unsigned int i;
+
+       for (i = 0; i < sizeof(*ip)/sizeof(u_int32_t); i++)
+               if (((u_int32_t *)ip)[i])
+                       return 0;
+
+       return 1;
+}
+
+static inline int
+check_match(const STRUCT_ENTRY_MATCH *m, unsigned int *off)
+{
+       assert(m->u.match_size >= sizeof(STRUCT_ENTRY_MATCH));
+       assert(ALIGN(m->u.match_size) == m->u.match_size);
+
+       (*off) += m->u.match_size;
+       return 0;
+}
+
+static inline int
+check_entry(const STRUCT_ENTRY *e, unsigned int *i, unsigned int *off,
+           unsigned int user_offset, int *was_return,
+           TC_HANDLE_T h)
+{
+       unsigned int toff;
+       STRUCT_STANDARD_TARGET *t;
+
+       assert(e->target_offset >= sizeof(STRUCT_ENTRY));
+       assert(e->next_offset >= e->target_offset
+              + sizeof(STRUCT_ENTRY_TARGET));
+       toff = sizeof(STRUCT_ENTRY);
+       IPT_MATCH_ITERATE(e, check_match, &toff);
+
+       assert(toff == e->target_offset);
+
+       t = (STRUCT_STANDARD_TARGET *)
+               GET_TARGET((STRUCT_ENTRY *)e);
+       /* next_offset will have to be multiple of entry alignment. */
+       assert(e->next_offset == ALIGN(e->next_offset));
+       assert(e->target_offset == ALIGN(e->target_offset));
+       assert(t->target.u.target_size == ALIGN(t->target.u.target_size));
+       assert(!TC_IS_CHAIN(t->target.u.user.name, h));
+
+       if (strcmp(t->target.u.user.name, STANDARD_TARGET) == 0) {
+               assert(t->target.u.target_size
+                      == ALIGN(sizeof(STRUCT_STANDARD_TARGET)));
+
+               assert(t->verdict == -NF_DROP-1
+                      || t->verdict == -NF_ACCEPT-1
+                      || t->verdict == RETURN
+                      || t->verdict < (int)h->entries->size);
+
+               if (t->verdict >= 0) {
+                       STRUCT_ENTRY *te = get_entry(h, t->verdict);
+                       int idx;
+
+                       idx = iptcb_entry2index(h, te);
+                       assert(strcmp(GET_TARGET(te)->u.user.name,
+                                     IPT_ERROR_TARGET)
+                              != 0);
+                       assert(te != e);
+
+                       /* Prior node must be error node, or this node. */
+                       assert(t->verdict == iptcb_entry2offset(h, e)+e->next_offset
+                              || strcmp(GET_TARGET(index2entry(h, idx-1))
+                                        ->u.user.name, IPT_ERROR_TARGET)
+                              == 0);
+               }
+
+               if (t->verdict == RETURN
+                   && unconditional(&e->ip)
+                   && e->target_offset == sizeof(*e))
+                       *was_return = 1;
+               else
+                       *was_return = 0;
+       } else if (strcmp(t->target.u.user.name, IPT_ERROR_TARGET) == 0) {
+               assert(t->target.u.target_size
+                      == ALIGN(sizeof(struct ipt_error_target)));
+
+               /* If this is in user area, previous must have been return */
+               if (*off > user_offset)
+                       assert(*was_return);
+
+               *was_return = 0;
+       }
+       else *was_return = 0;
+
+       if (*off == user_offset)
+               assert(strcmp(t->target.u.user.name, IPT_ERROR_TARGET) == 0);
+
+       (*off) += e->next_offset;
+       (*i)++;
+       return 0;
+}
+
+#ifdef IPTC_DEBUG
+/* Do every conceivable sanity check on the handle */
+static void
+do_check(TC_HANDLE_T h, unsigned int line)
+{
+       unsigned int i, n;
+       unsigned int user_offset; /* Offset of first user chain */
+       int was_return;
+
+       assert(h->changed == 0 || h->changed == 1);
+       if (strcmp(h->info.name, "filter") == 0) {
+               assert(h->info.valid_hooks
+                      == (1 << NF_IP_LOCAL_IN
+                          | 1 << NF_IP_FORWARD
+                          | 1 << NF_IP_LOCAL_OUT));
+
+               /* Hooks should be first three */
+               assert(h->info.hook_entry[NF_IP_LOCAL_IN] == 0);
+
+               n = get_chain_end(h, 0);
+               n += get_entry(h, n)->next_offset;
+               assert(h->info.hook_entry[NF_IP_FORWARD] == n);
+
+               n = get_chain_end(h, n);
+               n += get_entry(h, n)->next_offset;
+               assert(h->info.hook_entry[NF_IP_LOCAL_OUT] == n);
+
+               user_offset = h->info.hook_entry[NF_IP_LOCAL_OUT];
+       } else if (strcmp(h->info.name, "nat") == 0) {
+               assert((h->info.valid_hooks
+                       == (1 << NF_IP_PRE_ROUTING
+                           | 1 << NF_IP_POST_ROUTING
+                           | 1 << NF_IP_LOCAL_OUT)) ||
+                      (h->info.valid_hooks
+                       == (1 << NF_IP_PRE_ROUTING
+                           | 1 << NF_IP_LOCAL_IN
+                           | 1 << NF_IP_POST_ROUTING
+                           | 1 << NF_IP_LOCAL_OUT)));
+
+               assert(h->info.hook_entry[NF_IP_PRE_ROUTING] == 0);
+
+               n = get_chain_end(h, 0);
+
+               n += get_entry(h, n)->next_offset;
+               assert(h->info.hook_entry[NF_IP_POST_ROUTING] == n);
+               n = get_chain_end(h, n);
+
+               n += get_entry(h, n)->next_offset;
+               assert(h->info.hook_entry[NF_IP_LOCAL_OUT] == n);
+               user_offset = h->info.hook_entry[NF_IP_LOCAL_OUT];
+
+               if (h->info.valid_hooks & (1 << NF_IP_LOCAL_IN)) {
+                       n = get_chain_end(h, n);
+                       n += get_entry(h, n)->next_offset;
+                       assert(h->info.hook_entry[NF_IP_LOCAL_IN] == n);
+                       user_offset = h->info.hook_entry[NF_IP_LOCAL_IN];
+               }
+
+       } else if (strcmp(h->info.name, "mangle") == 0) {
+               /* This code is getting ugly because linux < 2.4.18-pre6 had
+                * two mangle hooks, linux >= 2.4.18-pre6 has five mangle hooks
+                * */
+               assert((h->info.valid_hooks
+                       == (1 << NF_IP_PRE_ROUTING
+                           | 1 << NF_IP_LOCAL_OUT)) || 
+                      (h->info.valid_hooks
+                       == (1 << NF_IP_PRE_ROUTING
+                           | 1 << NF_IP_LOCAL_IN
+                           | 1 << NF_IP_FORWARD
+                           | 1 << NF_IP_LOCAL_OUT
+                           | 1 << NF_IP_POST_ROUTING)));
+
+               /* Hooks should be first five */
+               assert(h->info.hook_entry[NF_IP_PRE_ROUTING] == 0);
+
+               n = get_chain_end(h, 0);
+
+               if (h->info.valid_hooks & (1 << NF_IP_LOCAL_IN)) {
+                       n += get_entry(h, n)->next_offset;
+                       assert(h->info.hook_entry[NF_IP_LOCAL_IN] == n);
+                       n = get_chain_end(h, n);
+               }
+
+               if (h->info.valid_hooks & (1 << NF_IP_FORWARD)) {
+                       n += get_entry(h, n)->next_offset;
+                       assert(h->info.hook_entry[NF_IP_FORWARD] == n);
+                       n = get_chain_end(h, n);
+               }
+
+               n += get_entry(h, n)->next_offset;
+               assert(h->info.hook_entry[NF_IP_LOCAL_OUT] == n);
+               user_offset = h->info.hook_entry[NF_IP_LOCAL_OUT];
+
+               if (h->info.valid_hooks & (1 << NF_IP_POST_ROUTING)) {
+                       n = get_chain_end(h, n);
+                       n += get_entry(h, n)->next_offset;
+                       assert(h->info.hook_entry[NF_IP_POST_ROUTING] == n);
+                       user_offset = h->info.hook_entry[NF_IP_POST_ROUTING];
+               }
+       } else if (strcmp(h->info.name, "raw") == 0) {
+               assert(h->info.valid_hooks
+                      == (1 << NF_IP_PRE_ROUTING
+                          | 1 << NF_IP_LOCAL_OUT));
+
+               /* Hooks should be first three */
+               assert(h->info.hook_entry[NF_IP_PRE_ROUTING] == 0);
+
+               n = get_chain_end(h, n);
+               n += get_entry(h, n)->next_offset;
+               assert(h->info.hook_entry[NF_IP_LOCAL_OUT] == n);
+
+               user_offset = h->info.hook_entry[NF_IP_LOCAL_OUT];
+
+#ifdef NF_IP_DROPPING
+       } else if (strcmp(h->info.name, "drop") == 0) {
+               assert(h->info.valid_hooks == (1 << NF_IP_DROPPING));
+
+               /* Hook should be first */
+               assert(h->info.hook_entry[NF_IP_DROPPING] == 0);
+               user_offset = 0;
+#endif
+       } else {
+               fprintf(stderr, "Unknown table `%s'\n", h->info.name);
+               abort();
+       }
+
+       /* User chain == end of last builtin + policy entry */
+       user_offset = get_chain_end(h, user_offset);
+       user_offset += get_entry(h, user_offset)->next_offset;
+
+       /* Overflows should be end of entry chains, and unconditional
+           policy nodes. */
+       for (i = 0; i < NUMHOOKS; i++) {
+               STRUCT_ENTRY *e;
+               STRUCT_STANDARD_TARGET *t;
+
+               if (!(h->info.valid_hooks & (1 << i)))
+                       continue;
+               assert(h->info.underflow[i]
+                      == get_chain_end(h, h->info.hook_entry[i]));
+
+               e = get_entry(h, get_chain_end(h, h->info.hook_entry[i]));
+               assert(unconditional(&e->ip));
+               assert(e->target_offset == sizeof(*e));
+               t = (STRUCT_STANDARD_TARGET *)GET_TARGET(e);
+               assert(t->target.u.target_size == ALIGN(sizeof(*t)));
+               assert(e->next_offset == sizeof(*e) + ALIGN(sizeof(*t)));
+
+               assert(strcmp(t->target.u.user.name, STANDARD_TARGET)==0);
+               assert(t->verdict == -NF_DROP-1 || t->verdict == -NF_ACCEPT-1);
+
+               /* Hooks and underflows must be valid entries */
+               entry2index(h, get_entry(h, h->info.hook_entry[i]));
+               entry2index(h, get_entry(h, h->info.underflow[i]));
+       }
+
+       assert(h->info.size
+              >= h->info.num_entries * (sizeof(STRUCT_ENTRY)
+                                        +sizeof(STRUCT_STANDARD_TARGET)));
+
+       assert(h->entries.size
+              >= (h->new_number
+                  * (sizeof(STRUCT_ENTRY)
+                     + sizeof(STRUCT_STANDARD_TARGET))));
+       assert(strcmp(h->info.name, h->entries.name) == 0);
+
+       i = 0; n = 0;
+       was_return = 0;
+       /* Check all the entries. */
+       ENTRY_ITERATE(h->entries.entrytable, h->entries.size,
+                     check_entry, &i, &n, user_offset, &was_return, h);
+
+       assert(i == h->new_number);
+       assert(n == h->entries.size);
+
+       /* Final entry must be error node */
+       assert(strcmp(GET_TARGET(index2entry(h, h->new_number-1))
+                     ->u.user.name,
+                     ERROR_TARGET) == 0);
+}
+#endif /*IPTC_DEBUG*/
+
+#endif
diff --git a/src/libiptc/libip6tc.c b/src/libiptc/libip6tc.c
new file mode 100644 (file)
index 0000000..b30bb28
--- /dev/null
@@ -0,0 +1,455 @@
+/**
+ * This file was imported from the iptables sources.
+ * Copyright (C) 1999-2008 Netfilter Core Team
+ *
+ * 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; only version 2 of the License is applicable.
+ *
+ * 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 St, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+/* Library which manipulates firewall rules.  Version 0.1. */
+
+/* Architecture of firewall rules is as follows:
+ *
+ * Chains go INPUT, FORWARD, OUTPUT then user chains.
+ * Each user chain starts with an ERROR node.
+ * Every chain ends with an unconditional jump: a RETURN for user chains,
+ * and a POLICY for built-ins.
+ */
+
+/* (C)1999 Paul ``Rusty'' Russell - Placed under the GNU GPL (See
+   COPYING for details). */
+
+#include <assert.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <arpa/inet.h>
+
+#ifdef DEBUG_CONNTRACK
+#define inline
+#endif
+
+#if !defined(__GLIBC__) || (__GLIBC__ < 2)
+typedef unsigned int socklen_t;
+#endif
+
+#include "libip6tc.h"
+
+#define HOOK_PRE_ROUTING       NF_IP6_PRE_ROUTING
+#define HOOK_LOCAL_IN          NF_IP6_LOCAL_IN
+#define HOOK_FORWARD           NF_IP6_FORWARD
+#define HOOK_LOCAL_OUT         NF_IP6_LOCAL_OUT
+#define HOOK_POST_ROUTING      NF_IP6_POST_ROUTING
+
+#define STRUCT_ENTRY_TARGET    struct ip6t_entry_target
+#define STRUCT_ENTRY           struct ip6t_entry
+#define STRUCT_ENTRY_MATCH     struct ip6t_entry_match
+#define STRUCT_GETINFO         struct ip6t_getinfo
+#define STRUCT_GET_ENTRIES     struct ip6t_get_entries
+#define STRUCT_COUNTERS                struct ip6t_counters
+#define STRUCT_COUNTERS_INFO   struct ip6t_counters_info
+#define STRUCT_STANDARD_TARGET struct ip6t_standard_target
+#define STRUCT_REPLACE         struct ip6t_replace
+
+#define STRUCT_TC_HANDLE       struct ip6tc_handle
+#define TC_HANDLE_T            ip6tc_handle_t
+
+#define ENTRY_ITERATE          IP6T_ENTRY_ITERATE
+#define TABLE_MAXNAMELEN       IP6T_TABLE_MAXNAMELEN
+#define FUNCTION_MAXNAMELEN    IP6T_FUNCTION_MAXNAMELEN
+
+#define GET_TARGET             ip6t_get_target
+
+#define ERROR_TARGET           IP6T_ERROR_TARGET
+#define NUMHOOKS               NF_IP6_NUMHOOKS
+
+#define IPT_CHAINLABEL         ip6t_chainlabel
+
+#define TC_DUMP_ENTRIES                dump_entries6
+#define TC_IS_CHAIN            ip6tc_is_chain
+#define TC_FIRST_CHAIN         ip6tc_first_chain
+#define TC_NEXT_CHAIN          ip6tc_next_chain
+#define TC_FIRST_RULE          ip6tc_first_rule
+#define TC_NEXT_RULE           ip6tc_next_rule
+#define TC_GET_TARGET          ip6tc_get_target
+#define TC_BUILTIN             ip6tc_builtin
+#define TC_GET_POLICY          ip6tc_get_policy
+#define TC_INSERT_ENTRY                ip6tc_insert_entry
+#define TC_REPLACE_ENTRY       ip6tc_replace_entry
+#define TC_APPEND_ENTRY                ip6tc_append_entry
+#define TC_DELETE_ENTRY                ip6tc_delete_entry
+#define TC_DELETE_NUM_ENTRY    ip6tc_delete_num_entry
+#define TC_CHECK_PACKET                ip6tc_check_packet
+#define TC_FLUSH_ENTRIES       ip6tc_flush_entries
+#define TC_ZERO_ENTRIES                ip6tc_zero_entries
+#define TC_ZERO_COUNTER                ip6tc_zero_counter
+#define TC_READ_COUNTER                ip6tc_read_counter
+#define TC_SET_COUNTER         ip6tc_set_counter
+#define TC_CREATE_CHAIN                ip6tc_create_chain
+#define TC_GET_REFERENCES      ip6tc_get_references
+#define TC_DELETE_CHAIN                ip6tc_delete_chain
+#define TC_RENAME_CHAIN                ip6tc_rename_chain
+#define TC_SET_POLICY          ip6tc_set_policy
+#define TC_GET_RAW_SOCKET      ip6tc_get_raw_socket
+#define TC_INIT                        ip6tc_init
+#define TC_FREE                        ip6tc_free
+#define TC_COMMIT              ip6tc_commit
+#define TC_STRERROR            ip6tc_strerror
+#define TC_NUM_RULES           ip6tc_num_rules
+#define TC_GET_RULE            ip6tc_get_rule
+
+#define TC_AF                  AF_INET6
+#define TC_IPPROTO             IPPROTO_IPV6
+
+#define SO_SET_REPLACE         IP6T_SO_SET_REPLACE
+#define SO_SET_ADD_COUNTERS    IP6T_SO_SET_ADD_COUNTERS
+#define SO_GET_INFO            IP6T_SO_GET_INFO
+#define SO_GET_ENTRIES         IP6T_SO_GET_ENTRIES
+#define SO_GET_VERSION         IP6T_SO_GET_VERSION
+
+#define STANDARD_TARGET                IP6T_STANDARD_TARGET
+#define LABEL_RETURN           IP6TC_LABEL_RETURN
+#define LABEL_ACCEPT           IP6TC_LABEL_ACCEPT
+#define LABEL_DROP             IP6TC_LABEL_DROP
+#define LABEL_QUEUE            IP6TC_LABEL_QUEUE
+
+#define ALIGN                  IP6T_ALIGN
+#define RETURN                 IP6T_RETURN
+
+#include "libiptc.c"
+
+#define BIT6(a, l) \
+ ((ntohl(a->in6_u.u6_addr32[(l) / 32]) >> (31 - ((l) & 31))) & 1)
+
+int
+ipv6_prefix_length(const struct in6_addr *a)
+{
+       int l, i;
+       for (l = 0; l < 128; l++) {
+               if (BIT6(a, l) == 0)
+                       break;
+       }
+       for (i = l + 1; i < 128; i++) {
+               if (BIT6(a, i) == 1)
+                       return -1;
+       }
+       return l;
+}
+
+static int
+dump_entry(struct ip6t_entry *e, const ip6tc_handle_t handle)
+{
+       size_t i;
+       char buf[40];
+       int len;
+       struct ip6t_entry_target *t;
+       
+       printf("Entry %u (%lu):\n", iptcb_entry2index(handle, e),
+              iptcb_entry2offset(handle, e));
+       puts("SRC IP: ");
+       inet_ntop(AF_INET6, &e->ipv6.src, buf, sizeof buf);
+       puts(buf);
+       putchar('/');
+       len = ipv6_prefix_length(&e->ipv6.smsk);
+       if (len != -1)
+               printf("%d", len);
+       else {
+               inet_ntop(AF_INET6, &e->ipv6.smsk, buf, sizeof buf);
+               puts(buf);
+       }
+       putchar('\n');
+       
+       puts("DST IP: ");
+       inet_ntop(AF_INET6, &e->ipv6.dst, buf, sizeof buf);
+       puts(buf);
+       putchar('/');
+       len = ipv6_prefix_length(&e->ipv6.dmsk);
+       if (len != -1)
+               printf("%d", len);
+       else {
+               inet_ntop(AF_INET6, &e->ipv6.dmsk, buf, sizeof buf);
+               puts(buf);
+       }
+       putchar('\n');
+       
+       printf("Interface: `%s'/", e->ipv6.iniface);
+       for (i = 0; i < IFNAMSIZ; i++)
+               printf("%c", e->ipv6.iniface_mask[i] ? 'X' : '.');
+       printf("to `%s'/", e->ipv6.outiface);
+       for (i = 0; i < IFNAMSIZ; i++)
+               printf("%c", e->ipv6.outiface_mask[i] ? 'X' : '.');
+       printf("\nProtocol: %u\n", e->ipv6.proto);
+       if (e->ipv6.flags & IP6T_F_TOS)
+               printf("TOS: %u\n", e->ipv6.tos);
+       printf("Flags: %02X\n", e->ipv6.flags);
+       printf("Invflags: %02X\n", e->ipv6.invflags);
+       printf("Counters: %llu packets, %llu bytes\n",
+              (unsigned long long)e->counters.pcnt, (unsigned long long)e->counters.bcnt);
+       printf("Cache: %08X\n", e->nfcache);
+       
+       IP6T_MATCH_ITERATE(e, print_match);
+
+       t = ip6t_get_target(e);
+       printf("Target name: `%s' [%u]\n", t->u.user.name, t->u.target_size);
+       if (strcmp(t->u.user.name, IP6T_STANDARD_TARGET) == 0) {
+               int pos = *(int *)t->data;
+               if (pos < 0)
+                       printf("verdict=%s\n",
+                              pos == -NF_ACCEPT-1 ? "NF_ACCEPT"
+                              : pos == -NF_DROP-1 ? "NF_DROP"
+                              : pos == IP6T_RETURN ? "RETURN"
+                              : "UNKNOWN");
+               else
+                       printf("verdict=%u\n", pos);
+       } else if (strcmp(t->u.user.name, IP6T_ERROR_TARGET) == 0)
+               printf("error=`%s'\n", t->data);
+
+       printf("\n");
+       return 0;
+}
+
+static unsigned char *
+is_same(const STRUCT_ENTRY *a, const STRUCT_ENTRY *b,
+       unsigned char *matchmask)
+{
+       unsigned int i;
+       unsigned char *mptr;
+
+       /* Always compare head structures: ignore mask here. */
+       if (memcmp(&a->ipv6.src, &b->ipv6.src, sizeof(struct in6_addr))
+           || memcmp(&a->ipv6.dst, &b->ipv6.dst, sizeof(struct in6_addr))
+           || memcmp(&a->ipv6.smsk, &b->ipv6.smsk, sizeof(struct in6_addr))
+           || memcmp(&a->ipv6.dmsk, &b->ipv6.dmsk, sizeof(struct in6_addr))
+           || a->ipv6.proto != b->ipv6.proto
+           || a->ipv6.tos != b->ipv6.tos
+           || a->ipv6.flags != b->ipv6.flags
+           || a->ipv6.invflags != b->ipv6.invflags)
+               return NULL;
+
+       for (i = 0; i < IFNAMSIZ; i++) {
+               if (a->ipv6.iniface_mask[i] != b->ipv6.iniface_mask[i])
+                       return NULL;
+               if ((a->ipv6.iniface[i] & a->ipv6.iniface_mask[i])
+                   != (b->ipv6.iniface[i] & b->ipv6.iniface_mask[i]))
+                       return NULL;
+               if (a->ipv6.outiface_mask[i] != b->ipv6.outiface_mask[i])
+                       return NULL;
+               if ((a->ipv6.outiface[i] & a->ipv6.outiface_mask[i])
+                   != (b->ipv6.outiface[i] & b->ipv6.outiface_mask[i]))
+                       return NULL;
+       }
+
+       if (a->target_offset != b->target_offset
+           || a->next_offset != b->next_offset)
+               return NULL;
+
+       mptr = matchmask + sizeof(STRUCT_ENTRY);
+       if (IP6T_MATCH_ITERATE(a, match_different, a->elems, b->elems, &mptr))
+               return NULL;
+       mptr += IP6T_ALIGN(sizeof(struct ip6t_entry_target));
+
+       return mptr;
+}
+
+/* All zeroes == unconditional rule. */
+static inline int
+unconditional(const struct ip6t_ip6 *ipv6)
+{
+       unsigned int i;
+
+       for (i = 0; i < sizeof(*ipv6); i++)
+               if (((char *)ipv6)[i])
+                       break;
+
+       return (i == sizeof(*ipv6));
+}
+
+#ifdef IPTC_DEBUG
+/* Do every conceivable sanity check on the handle */
+static void
+do_check(TC_HANDLE_T h, unsigned int line)
+{
+       unsigned int i, n;
+       unsigned int user_offset; /* Offset of first user chain */
+       int was_return;
+
+       assert(h->changed == 0 || h->changed == 1);
+       if (strcmp(h->info.name, "filter") == 0) {
+               assert(h->info.valid_hooks
+                      == (1 << NF_IP6_LOCAL_IN
+                          | 1 << NF_IP6_FORWARD
+                          | 1 << NF_IP6_LOCAL_OUT));
+
+               /* Hooks should be first three */
+               assert(h->info.hook_entry[NF_IP6_LOCAL_IN] == 0);
+
+               n = get_chain_end(h, 0);
+               n += get_entry(h, n)->next_offset;
+               assert(h->info.hook_entry[NF_IP6_FORWARD] == n);
+
+               n = get_chain_end(h, n);
+               n += get_entry(h, n)->next_offset;
+               assert(h->info.hook_entry[NF_IP6_LOCAL_OUT] == n);
+
+               user_offset = h->info.hook_entry[NF_IP6_LOCAL_OUT];
+       } else if (strcmp(h->info.name, "nat") == 0) {
+               assert((h->info.valid_hooks
+                       == (1 << NF_IP6_PRE_ROUTING
+                           | 1 << NF_IP6_LOCAL_OUT
+                           | 1 << NF_IP6_POST_ROUTING)) ||
+                      (h->info.valid_hooks
+                       == (1 << NF_IP6_PRE_ROUTING
+                           | 1 << NF_IP6_LOCAL_IN
+                           | 1 << NF_IP6_LOCAL_OUT
+                           | 1 << NF_IP6_POST_ROUTING)));
+
+               assert(h->info.hook_entry[NF_IP6_PRE_ROUTING] == 0);
+
+               n = get_chain_end(h, 0);
+
+               n += get_entry(h, n)->next_offset;
+               assert(h->info.hook_entry[NF_IP6_POST_ROUTING] == n);
+               n = get_chain_end(h, n);
+
+               n += get_entry(h, n)->next_offset;
+               assert(h->info.hook_entry[NF_IP6_LOCAL_OUT] == n);
+               user_offset = h->info.hook_entry[NF_IP6_LOCAL_OUT];
+
+               if (h->info.valid_hooks & (1 << NF_IP6_LOCAL_IN)) {
+                       n = get_chain_end(h, n);
+                       n += get_entry(h, n)->next_offset;
+                       assert(h->info.hook_entry[NF_IP6_LOCAL_IN] == n);
+                       user_offset = h->info.hook_entry[NF_IP6_LOCAL_IN];
+               }
+
+       } else if (strcmp(h->info.name, "mangle") == 0) {
+               /* This code is getting ugly because linux < 2.4.18-pre6 had
+                * two mangle hooks, linux >= 2.4.18-pre6 has five mangle hooks
+                * */
+               assert((h->info.valid_hooks
+                       == (1 << NF_IP6_PRE_ROUTING
+                           | 1 << NF_IP6_LOCAL_OUT)) ||
+                      (h->info.valid_hooks
+                       == (1 << NF_IP6_PRE_ROUTING
+                           | 1 << NF_IP6_LOCAL_IN
+                           | 1 << NF_IP6_FORWARD
+                           | 1 << NF_IP6_LOCAL_OUT
+                           | 1 << NF_IP6_POST_ROUTING)));
+
+               /* Hooks should be first five */
+               assert(h->info.hook_entry[NF_IP6_PRE_ROUTING] == 0);
+
+               n = get_chain_end(h, 0);
+
+               if (h->info.valid_hooks & (1 << NF_IP6_LOCAL_IN)) {
+                       n += get_entry(h, n)->next_offset;
+                       assert(h->info.hook_entry[NF_IP6_LOCAL_IN] == n);
+                       n = get_chain_end(h, n);
+               }
+
+               if (h->info.valid_hooks & (1 << NF_IP6_FORWARD)) {
+                       n += get_entry(h, n)->next_offset;
+                       assert(h->info.hook_entry[NF_IP6_FORWARD] == n);
+                       n = get_chain_end(h, n);
+               }
+
+               n += get_entry(h, n)->next_offset;
+               assert(h->info.hook_entry[NF_IP6_LOCAL_OUT] == n);
+               user_offset = h->info.hook_entry[NF_IP6_LOCAL_OUT];
+
+               if (h->info.valid_hooks & (1 << NF_IP6_POST_ROUTING)) {
+                       n = get_chain_end(h, n);
+                       n += get_entry(h, n)->next_offset;
+                       assert(h->info.hook_entry[NF_IP6_POST_ROUTING] == n);
+                       user_offset = h->info.hook_entry[NF_IP6_POST_ROUTING];
+               }
+       } else if (strcmp(h->info.name, "raw") == 0) {
+               assert(h->info.valid_hooks
+                      == (1 << NF_IP6_PRE_ROUTING
+                          | 1 << NF_IP6_LOCAL_OUT));
+
+               /* Hooks should be first three */
+               assert(h->info.hook_entry[NF_IP6_PRE_ROUTING] == 0);
+
+               n = get_chain_end(h, n);
+               n += get_entry(h, n)->next_offset;
+               assert(h->info.hook_entry[NF_IP6_LOCAL_OUT] == n);
+
+               user_offset = h->info.hook_entry[NF_IP6_LOCAL_OUT];
+       } else {
+                fprintf(stderr, "Unknown table `%s'\n", h->info.name);
+               abort();
+       }
+
+       /* User chain == end of last builtin + policy entry */
+       user_offset = get_chain_end(h, user_offset);
+       user_offset += get_entry(h, user_offset)->next_offset;
+
+       /* Overflows should be end of entry chains, and unconditional
+           policy nodes. */
+       for (i = 0; i < NUMHOOKS; i++) {
+               STRUCT_ENTRY *e;
+               STRUCT_STANDARD_TARGET *t;
+
+               if (!(h->info.valid_hooks & (1 << i)))
+                       continue;
+               assert(h->info.underflow[i]
+                      == get_chain_end(h, h->info.hook_entry[i]));
+
+               e = get_entry(h, get_chain_end(h, h->info.hook_entry[i]));
+               assert(unconditional(&e->ipv6));
+               assert(e->target_offset == sizeof(*e));
+               t = (STRUCT_STANDARD_TARGET *)GET_TARGET(e);
+               printf("target_size=%u, align=%u\n",
+                       t->target.u.target_size, ALIGN(sizeof(*t)));
+               assert(t->target.u.target_size == ALIGN(sizeof(*t)));
+               assert(e->next_offset == sizeof(*e) + ALIGN(sizeof(*t)));
+
+               assert(strcmp(t->target.u.user.name, STANDARD_TARGET)==0);
+               assert(t->verdict == -NF_DROP-1 || t->verdict == -NF_ACCEPT-1);
+
+               /* Hooks and underflows must be valid entries */
+               iptcb_entry2index(h, get_entry(h, h->info.hook_entry[i]));
+               iptcb_entry2index(h, get_entry(h, h->info.underflow[i]));
+       }
+
+       assert(h->info.size
+              >= h->info.num_entries * (sizeof(STRUCT_ENTRY)
+                                        +sizeof(STRUCT_STANDARD_TARGET)));
+
+       assert(h->entries.size
+              >= (h->new_number
+                  * (sizeof(STRUCT_ENTRY)
+                     + sizeof(STRUCT_STANDARD_TARGET))));
+       assert(strcmp(h->info.name, h->entries.name) == 0);
+
+       i = 0; n = 0;
+       was_return = 0;
+
+#if 0
+       /* Check all the entries. */
+       ENTRY_ITERATE(h->entries.entrytable, h->entries.size,
+                     check_entry, &i, &n, user_offset, &was_return, h);
+
+       assert(i == h->new_number);
+       assert(n == h->entries.size);
+
+       /* Final entry must be error node */
+       assert(strcmp(GET_TARGET(index2entry(h, h->new_number-1))
+                     ->u.user.name,
+                     ERROR_TARGET) == 0);
+#endif
+}
+#endif /*IPTC_DEBUG*/
diff --git a/src/libiptc/libip6tc.h b/src/libiptc/libip6tc.h
new file mode 100644 (file)
index 0000000..103267b
--- /dev/null
@@ -0,0 +1,173 @@
+/**
+ * This file was imported from the iptables sources.
+ * Copyright (C) 1999-2008 Netfilter Core Team
+ *
+ * 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; only version 2 of the License is applicable.
+ *
+ * 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 St, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+#ifndef _LIBIP6TC_H
+#define _LIBIP6TC_H
+/* Library which manipulates firewall rules. Version 0.2. */
+
+#include <linux/types.h>
+#include "ipt_kernel_headers.h"
+#include <linux/netfilter_ipv6/ip6_tables.h>
+
+#ifndef IP6T_MIN_ALIGN
+#define IP6T_MIN_ALIGN (__alignof__(struct ip6t_entry))
+#endif
+#define IP6T_ALIGN(s) (((s) + (IP6T_MIN_ALIGN-1)) & ~(IP6T_MIN_ALIGN-1))
+
+typedef char ip6t_chainlabel[32];
+
+#define IP6TC_LABEL_ACCEPT "ACCEPT"
+#define IP6TC_LABEL_DROP "DROP"
+#define IP6TC_LABEL_QUEUE   "QUEUE"
+#define IP6TC_LABEL_RETURN "RETURN"
+
+/* Transparent handle type. */
+typedef struct ip6tc_handle *ip6tc_handle_t;
+
+/* Does this chain exist? */
+int ip6tc_is_chain(const char *chain, const ip6tc_handle_t handle);
+
+/* Take a snapshot of the rules. Returns NULL on error. */
+ip6tc_handle_t ip6tc_init(const char *tablename);
+
+/* Cleanup after ip6tc_init(). */
+void ip6tc_free(ip6tc_handle_t *h);
+
+/* Iterator functions to run through the chains.  Returns NULL at end. */
+const char *ip6tc_first_chain(ip6tc_handle_t *handle);
+const char *ip6tc_next_chain(ip6tc_handle_t *handle);
+
+/* Get first rule in the given chain: NULL for empty chain. */
+const struct ip6t_entry *ip6tc_first_rule(const char *chain,
+                                         ip6tc_handle_t *handle);
+
+/* Returns NULL when rules run out. */
+const struct ip6t_entry *ip6tc_next_rule(const struct ip6t_entry *prev,
+                                        ip6tc_handle_t *handle);
+
+/* Returns a pointer to the target name of this position. */
+const char *ip6tc_get_target(const struct ip6t_entry *e,
+                            ip6tc_handle_t *handle);
+
+/* Is this a built-in chain? */
+int ip6tc_builtin(const char *chain, const ip6tc_handle_t handle);
+
+/* Get the policy of a given built-in chain */
+const char *ip6tc_get_policy(const char *chain,
+                            struct ip6t_counters *counters,
+                            ip6tc_handle_t *handle);
+
+/* These functions return TRUE for OK or 0 and set errno. If errno ==
+   0, it means there was a version error (ie. upgrade libiptc). */
+/* Rule numbers start at 1 for the first rule. */
+
+/* Insert the entry `fw' in chain `chain' into position `rulenum'. */
+int ip6tc_insert_entry(const ip6t_chainlabel chain,
+                      const struct ip6t_entry *e,
+                      unsigned int rulenum,
+                      ip6tc_handle_t *handle);
+
+/* Atomically replace rule `rulenum' in `chain' with `fw'. */
+int ip6tc_replace_entry(const ip6t_chainlabel chain,
+                       const struct ip6t_entry *e,
+                       unsigned int rulenum,
+                       ip6tc_handle_t *handle);
+
+/* Append entry `fw' to chain `chain'. Equivalent to insert with
+   rulenum = length of chain. */
+int ip6tc_append_entry(const ip6t_chainlabel chain,
+                      const struct ip6t_entry *e,
+                      ip6tc_handle_t *handle);
+
+/* Delete the first rule in `chain' which matches `fw'. */
+int ip6tc_delete_entry(const ip6t_chainlabel chain,
+                      const struct ip6t_entry *origfw,
+                      unsigned char *matchmask,
+                      ip6tc_handle_t *handle);
+
+/* Delete the rule in position `rulenum' in `chain'. */
+int ip6tc_delete_num_entry(const ip6t_chainlabel chain,
+                          unsigned int rulenum,
+                          ip6tc_handle_t *handle);
+
+/* Check the packet `fw' on chain `chain'. Returns the verdict, or
+   NULL and sets errno. */
+const char *ip6tc_check_packet(const ip6t_chainlabel chain,
+                              struct ip6t_entry *,
+                              ip6tc_handle_t *handle);
+
+/* Flushes the entries in the given chain (ie. empties chain). */
+int ip6tc_flush_entries(const ip6t_chainlabel chain,
+                       ip6tc_handle_t *handle);
+
+/* Zeroes the counters in a chain. */
+int ip6tc_zero_entries(const ip6t_chainlabel chain,
+                      ip6tc_handle_t *handle);
+
+/* Creates a new chain. */
+int ip6tc_create_chain(const ip6t_chainlabel chain,
+                      ip6tc_handle_t *handle);
+
+/* Deletes a chain. */
+int ip6tc_delete_chain(const ip6t_chainlabel chain,
+                      ip6tc_handle_t *handle);
+
+/* Renames a chain. */
+int ip6tc_rename_chain(const ip6t_chainlabel oldname,
+                      const ip6t_chainlabel newname,
+                      ip6tc_handle_t *handle);
+
+/* Sets the policy on a built-in chain. */
+int ip6tc_set_policy(const ip6t_chainlabel chain,
+                    const ip6t_chainlabel policy,
+                    struct ip6t_counters *counters,
+                    ip6tc_handle_t *handle);
+
+/* Get the number of references to this chain */
+int ip6tc_get_references(unsigned int *ref, const ip6t_chainlabel chain,
+                        ip6tc_handle_t *handle);
+
+/* read packet and byte counters for a specific rule */
+struct ip6t_counters *ip6tc_read_counter(const ip6t_chainlabel chain,
+                                       unsigned int rulenum,
+                                       ip6tc_handle_t *handle);
+
+/* zero packet and byte counters for a specific rule */
+int ip6tc_zero_counter(const ip6t_chainlabel chain,
+                      unsigned int rulenum,
+                      ip6tc_handle_t *handle);
+
+/* set packet and byte counters for a specific rule */
+int ip6tc_set_counter(const ip6t_chainlabel chain,
+                     unsigned int rulenum,
+                     struct ip6t_counters *counters,
+                     ip6tc_handle_t *handle);
+
+/* Makes the actual changes. */
+int ip6tc_commit(ip6tc_handle_t *handle);
+
+/* Get raw socket. */
+int ip6tc_get_raw_socket();
+
+/* Translates errno numbers into more human-readable form than strerror. */
+const char *ip6tc_strerror(int err);
+
+/* Return prefix length, or -1 if not contiguous */
+int ipv6_prefix_length(const struct in6_addr *a);
+
+#endif /* _LIBIP6TC_H */
diff --git a/src/libiptc/libiptc.c b/src/libiptc/libiptc.c
new file mode 100644 (file)
index 0000000..f8e88b2
--- /dev/null
@@ -0,0 +1,2296 @@
+/**
+ * This file was imported from the iptables sources.
+ * Copyright (C) 1999-2008 Netfilter Core Team
+ *
+ * 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; only version 2 of the License is applicable.
+ *
+ * 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 St, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+/* Library which manipulates firewall rules.  Version $Revision: 7138 $ */
+
+/* Architecture of firewall rules is as follows:
+ *
+ * Chains go INPUT, FORWARD, OUTPUT then user chains.
+ * Each user chain starts with an ERROR node.
+ * Every chain ends with an unconditional jump: a RETURN for user chains,
+ * and a POLICY for built-ins.
+ */
+
+/* (C) 1999 Paul ``Rusty'' Russell - Placed under the GNU GPL (See
+ * COPYING for details). 
+ * (C) 2000-2004 by the Netfilter Core Team <coreteam@netfilter.org>
+ *
+ * 2003-Jun-20: Harald Welte <laforge@netfilter.org>:
+ *     - Reimplementation of chain cache to use offsets instead of entries
+ * 2003-Jun-23: Harald Welte <laforge@netfilter.org>:
+ *     - performance optimization, sponsored by Astaro AG (http://www.astaro.com/)
+ *       don't rebuild the chain cache after every operation, instead fix it
+ *       up after a ruleset change.  
+ * 2004-Aug-18: Harald Welte <laforge@netfilter.org>:
+ *     - futher performance work: total reimplementation of libiptc.
+ *     - libiptc now has a real internal (linked-list) represntation of the
+ *       ruleset and a parser/compiler from/to this internal representation
+ *     - again sponsored by Astaro AG (http://www.astaro.com/)
+ */
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include "linux_list.h"
+
+//#define IPTC_DEBUG2 1
+
+#ifdef IPTC_DEBUG2
+#include <fcntl.h>
+#define DEBUGP(x, args...)     fprintf(stderr, "%s: " x, __FUNCTION__, ## args)
+#define DEBUGP_C(x, args...)   fprintf(stderr, x, ## args)
+#else
+#define DEBUGP(x, args...)
+#define DEBUGP_C(x, args...)
+#endif
+
+#ifndef IPT_LIB_DIR
+#define IPT_LIB_DIR "/usr/local/lib/iptables"
+#endif
+
+static int sockfd = -1;
+static int sockfd_use = 0;
+static void *iptc_fn = NULL;
+
+static const char *hooknames[] = {
+       [HOOK_PRE_ROUTING]      = "PREROUTING",
+       [HOOK_LOCAL_IN]         = "INPUT",
+       [HOOK_FORWARD]          = "FORWARD",
+       [HOOK_LOCAL_OUT]        = "OUTPUT",
+       [HOOK_POST_ROUTING]     = "POSTROUTING",
+#ifdef HOOK_DROPPING
+       [HOOK_DROPPING]         = "DROPPING"
+#endif
+};
+
+/* Convenience structures */
+struct ipt_error_target
+{
+       STRUCT_ENTRY_TARGET t;
+       char error[TABLE_MAXNAMELEN];
+};
+
+struct chain_head;
+struct rule_head;
+
+struct counter_map
+{
+       enum {
+               COUNTER_MAP_NOMAP,
+               COUNTER_MAP_NORMAL_MAP,
+               COUNTER_MAP_ZEROED,
+               COUNTER_MAP_SET
+       } maptype;
+       unsigned int mappos;
+};
+
+enum iptcc_rule_type {
+       IPTCC_R_STANDARD,               /* standard target (ACCEPT, ...) */
+       IPTCC_R_MODULE,                 /* extension module (SNAT, ...) */
+       IPTCC_R_FALLTHROUGH,            /* fallthrough rule */
+       IPTCC_R_JUMP,                   /* jump to other chain */
+};
+
+struct rule_head
+{
+       struct list_head list;
+       struct chain_head *chain;
+       struct counter_map counter_map;
+
+       unsigned int index;             /* index (needed for counter_map) */
+       unsigned int offset;            /* offset in rule blob */
+
+       enum iptcc_rule_type type;
+       struct chain_head *jump;        /* jump target, if IPTCC_R_JUMP */
+
+       unsigned int size;              /* size of entry data */
+       STRUCT_ENTRY entry[0];
+};
+
+struct chain_head
+{
+       struct list_head list;
+       char name[TABLE_MAXNAMELEN];
+       unsigned int hooknum;           /* hook number+1 if builtin */
+       unsigned int references;        /* how many jumps reference us */
+       int verdict;                    /* verdict if builtin */
+
+       STRUCT_COUNTERS counters;       /* per-chain counters */
+       struct counter_map counter_map;
+
+       unsigned int num_rules;         /* number of rules in list */
+       struct list_head rules;         /* list of rules */
+
+       unsigned int index;             /* index (needed for jump resolval) */
+       unsigned int head_offset;       /* offset in rule blob */
+       unsigned int foot_index;        /* index (needed for counter_map) */
+       unsigned int foot_offset;       /* offset in rule blob */
+};
+
+STRUCT_TC_HANDLE
+{
+       int changed;                     /* Have changes been made? */
+
+       struct list_head chains;
+       
+       struct chain_head *chain_iterator_cur;
+       struct rule_head *rule_iterator_cur;
+
+       STRUCT_GETINFO info;
+       STRUCT_GET_ENTRIES *entries;
+};
+
+/* allocate a new chain head for the cache */
+static struct chain_head *iptcc_alloc_chain_head(const char *name, int hooknum)
+{
+       struct chain_head *c = malloc(sizeof(*c));
+       if (!c)
+               return NULL;
+       memset(c, 0, sizeof(*c));
+
+       strncpy(c->name, name, TABLE_MAXNAMELEN);
+       c->hooknum = hooknum;
+       INIT_LIST_HEAD(&c->rules);
+
+       return c;
+}
+
+/* allocate and initialize a new rule for the cache */
+static struct rule_head *iptcc_alloc_rule(struct chain_head *c, unsigned int size)
+{
+       struct rule_head *r = malloc(sizeof(*r)+size);
+       if (!r)
+               return NULL;
+       memset(r, 0, sizeof(*r));
+
+       r->chain = c;
+       r->size = size;
+
+       return r;
+}
+
+/* notify us that the ruleset has been modified by the user */
+static void
+set_changed(TC_HANDLE_T h)
+{
+       h->changed = 1;
+}
+
+#ifdef IPTC_DEBUG
+static void do_check(TC_HANDLE_T h, unsigned int line);
+#define CHECK(h) do { if (!getenv("IPTC_NO_CHECK")) do_check((h), __LINE__); } while(0)
+#else
+#define CHECK(h)
+#endif
+
+
+/**********************************************************************
+ * iptc blob utility functions (iptcb_*)
+ **********************************************************************/
+
+static inline int
+iptcb_get_number(const STRUCT_ENTRY *i,
+          const STRUCT_ENTRY *seek,
+          unsigned int *pos)
+{
+       if (i == seek)
+               return 1;
+       (*pos)++;
+       return 0;
+}
+
+static inline int
+iptcb_get_entry_n(STRUCT_ENTRY *i,
+           unsigned int number,
+           unsigned int *pos,
+           STRUCT_ENTRY **pe)
+{
+       if (*pos == number) {
+               *pe = i;
+               return 1;
+       }
+       (*pos)++;
+       return 0;
+}
+
+static inline STRUCT_ENTRY *
+iptcb_get_entry(TC_HANDLE_T h, unsigned int offset)
+{
+       return (STRUCT_ENTRY *)((char *)h->entries->entrytable + offset);
+}
+
+static unsigned int
+iptcb_entry2index(const TC_HANDLE_T h, const STRUCT_ENTRY *seek)
+{
+       unsigned int pos = 0;
+
+       if (ENTRY_ITERATE(h->entries->entrytable, h->entries->size,
+                         iptcb_get_number, seek, &pos) == 0) {
+               fprintf(stderr, "ERROR: offset %u not an entry!\n",
+                       (unsigned int)((char *)seek - (char *)h->entries->entrytable));
+               abort();
+       }
+       return pos;
+}
+
+static inline STRUCT_ENTRY *
+iptcb_offset2entry(TC_HANDLE_T h, unsigned int offset)
+{
+       return (STRUCT_ENTRY *) ((void *)h->entries->entrytable+offset);
+}
+
+
+static inline unsigned long
+iptcb_entry2offset(const TC_HANDLE_T h, const STRUCT_ENTRY *e)
+{
+       return (void *)e - (void *)h->entries->entrytable;
+}
+
+static inline unsigned int
+iptcb_offset2index(const TC_HANDLE_T h, unsigned int offset)
+{
+       return iptcb_entry2index(h, iptcb_offset2entry(h, offset));
+}
+
+/* Returns 0 if not hook entry, else hooknumber + 1 */
+static inline unsigned int
+iptcb_ent_is_hook_entry(STRUCT_ENTRY *e, TC_HANDLE_T h)
+{
+       unsigned int i;
+
+       for (i = 0; i < NUMHOOKS; i++) {
+               if ((h->info.valid_hooks & (1 << i))
+                   && iptcb_get_entry(h, h->info.hook_entry[i]) == e)
+                       return i+1;
+       }
+       return 0;
+}
+
+
+/**********************************************************************
+ * iptc cache utility functions (iptcc_*)
+ **********************************************************************/
+
+/* Is the given chain builtin (1) or user-defined (0) */
+static unsigned int iptcc_is_builtin(struct chain_head *c)
+{
+       return (c->hooknum ? 1 : 0);
+}
+
+/* Get a specific rule within a chain */
+static struct rule_head *iptcc_get_rule_num(struct chain_head *c,
+                                           unsigned int rulenum)
+{
+       struct rule_head *r;
+       unsigned int num = 0;
+
+       list_for_each_entry(r, &c->rules, list) {
+               num++;
+               if (num == rulenum)
+                       return r;
+       }
+       return NULL;
+}
+
+/* Get a specific rule within a chain backwards */
+static struct rule_head *iptcc_get_rule_num_reverse(struct chain_head *c,
+                                           unsigned int rulenum)
+{
+       struct rule_head *r;
+       unsigned int num = 0;
+
+       list_for_each_entry_reverse(r, &c->rules, list) {
+               num++;
+               if (num == rulenum)
+                       return r;
+       }
+       return NULL;
+}
+
+/* Returns chain head if found, otherwise NULL. */
+static struct chain_head *
+iptcc_find_chain_by_offset(TC_HANDLE_T handle, unsigned int offset)
+{
+       struct list_head *pos;
+
+       if (list_empty(&handle->chains))
+               return NULL;
+
+       list_for_each(pos, &handle->chains) {
+               struct chain_head *c = list_entry(pos, struct chain_head, list);
+               if (offset >= c->head_offset && offset <= c->foot_offset)
+                       return c;
+       }
+
+       return NULL;
+}
+/* Returns chain head if found, otherwise NULL. */
+static struct chain_head *
+iptcc_find_label(const char *name, TC_HANDLE_T handle)
+{
+       struct list_head *pos;
+
+       if (list_empty(&handle->chains))
+               return NULL;
+
+       list_for_each(pos, &handle->chains) {
+               struct chain_head *c = list_entry(pos, struct chain_head, list);
+               if (!strcmp(c->name, name))
+                       return c;
+       }
+
+       return NULL;
+}
+
+/* called when rule is to be removed from cache */
+static void iptcc_delete_rule(struct rule_head *r)
+{
+       DEBUGP("deleting rule %p (offset %u)\n", r, r->offset);
+       /* clean up reference count of called chain */
+       if (r->type == IPTCC_R_JUMP
+           && r->jump)
+               r->jump->references--;
+
+       list_del(&r->list);
+       free(r);
+}
+
+
+/**********************************************************************
+ * RULESET PARSER (blob -> cache)
+ **********************************************************************/
+
+/* Delete policy rule of previous chain, since cache doesn't contain
+ * chain policy rules.
+ * WARNING: This function has ugly design and relies on a lot of context, only
+ * to be called from specific places within the parser */
+static int __iptcc_p_del_policy(TC_HANDLE_T h, unsigned int num)
+{
+       if (h->chain_iterator_cur) {
+               /* policy rule is last rule */
+               struct rule_head *pr = (struct rule_head *)
+                       h->chain_iterator_cur->rules.prev;
+
+               /* save verdict */
+               h->chain_iterator_cur->verdict = 
+                       *(int *)GET_TARGET(pr->entry)->data;
+
+               /* save counter and counter_map information */
+               h->chain_iterator_cur->counter_map.maptype = 
+                                               COUNTER_MAP_NORMAL_MAP;
+               h->chain_iterator_cur->counter_map.mappos = num-1;
+               memcpy(&h->chain_iterator_cur->counters, &pr->entry->counters, 
+                       sizeof(h->chain_iterator_cur->counters));
+
+               /* foot_offset points to verdict rule */
+               h->chain_iterator_cur->foot_index = num;
+               h->chain_iterator_cur->foot_offset = pr->offset;
+
+               /* delete rule from cache */
+               iptcc_delete_rule(pr);
+               h->chain_iterator_cur->num_rules--;
+
+               return 1;
+       }
+       return 0;
+}
+
+/* alphabetically insert a chain into the list */
+static inline void iptc_insert_chain(TC_HANDLE_T h, struct chain_head *c)
+{
+       struct chain_head *tmp;
+
+       /* sort only user defined chains */
+       if (!c->hooknum) {
+               list_for_each_entry(tmp, &h->chains, list) {
+                       if (!tmp->hooknum && strcmp(c->name, tmp->name) <= 0) {
+                               list_add(&c->list, tmp->list.prev);
+                               return;
+                       }
+               }
+       }
+
+       /* survived till end of list: add at tail */
+       list_add_tail(&c->list, &h->chains);
+}
+
+/* Another ugly helper function split out of cache_add_entry to make it less
+ * spaghetti code */
+static void __iptcc_p_add_chain(TC_HANDLE_T h, struct chain_head *c,
+                               unsigned int offset, unsigned int *num)
+{
+       struct list_head  *tail = h->chains.prev;
+       struct chain_head *ctail;
+
+       __iptcc_p_del_policy(h, *num);
+
+       c->head_offset = offset;
+       c->index = *num;
+
+       /* Chains from kernel are already sorted, as they are inserted
+        * sorted. But there exists an issue when shifting to 1.4.0
+        * from an older version, as old versions allow last created
+        * chain to be unsorted.
+        */
+       if (iptcc_is_builtin(c)) /* Only user defined chains are sorted*/
+               list_add_tail(&c->list, &h->chains);
+       else {
+               ctail = list_entry(tail, struct chain_head, list);
+               if (strcmp(c->name, ctail->name) > 0)
+                       list_add_tail(&c->list, &h->chains);/* Already sorted*/
+               else
+                       iptc_insert_chain(h, c);/* Was not sorted */
+       }
+
+       h->chain_iterator_cur = c;
+}
+
+/* main parser function: add an entry from the blob to the cache */
+static int cache_add_entry(STRUCT_ENTRY *e, 
+                          TC_HANDLE_T h, 
+                          STRUCT_ENTRY **prev,
+                          unsigned int *num)
+{
+       unsigned int builtin;
+       unsigned int offset = (char *)e - (char *)h->entries->entrytable;
+
+       DEBUGP("entering...");
+
+       /* Last entry ("policy rule"). End it.*/
+       if (iptcb_entry2offset(h,e) + e->next_offset == h->entries->size) {
+               /* This is the ERROR node at the end of the chain */
+               DEBUGP_C("%u:%u: end of table:\n", *num, offset);
+
+               __iptcc_p_del_policy(h, *num);
+
+               h->chain_iterator_cur = NULL;
+               goto out_inc;
+       }
+
+       /* We know this is the start of a new chain if it's an ERROR
+        * target, or a hook entry point */
+
+       if (strcmp(GET_TARGET(e)->u.user.name, ERROR_TARGET) == 0) {
+               struct chain_head *c = 
+                       iptcc_alloc_chain_head((const char *)GET_TARGET(e)->data, 0);
+               DEBUGP_C("%u:%u:new userdefined chain %s: %p\n", *num, offset, 
+                       (char *)c->name, c);
+               if (!c) {
+                       errno = -ENOMEM;
+                       return -1;
+               }
+
+               __iptcc_p_add_chain(h, c, offset, num);
+
+       } else if ((builtin = iptcb_ent_is_hook_entry(e, h)) != 0) {
+               struct chain_head *c =
+                       iptcc_alloc_chain_head((char *)hooknames[builtin-1], 
+                                               builtin);
+               DEBUGP_C("%u:%u new builtin chain: %p (rules=%p)\n", 
+                       *num, offset, c, &c->rules);
+               if (!c) {
+                       errno = -ENOMEM;
+                       return -1;
+               }
+
+               c->hooknum = builtin;
+
+               __iptcc_p_add_chain(h, c, offset, num);
+
+               /* FIXME: this is ugly. */
+               goto new_rule;
+       } else {
+               /* has to be normal rule */
+               struct rule_head *r;
+new_rule:
+
+               if (!(r = iptcc_alloc_rule(h->chain_iterator_cur, 
+                                          e->next_offset))) {
+                       errno = ENOMEM;
+                       return -1;
+               }
+               DEBUGP_C("%u:%u normal rule: %p: ", *num, offset, r);
+
+               r->index = *num;
+               r->offset = offset;
+               memcpy(r->entry, e, e->next_offset);
+               r->counter_map.maptype = COUNTER_MAP_NORMAL_MAP;
+               r->counter_map.mappos = r->index;
+
+               /* handling of jumps, etc. */
+               if (!strcmp(GET_TARGET(e)->u.user.name, STANDARD_TARGET)) {
+                       STRUCT_STANDARD_TARGET *t;
+
+                       t = (STRUCT_STANDARD_TARGET *)GET_TARGET(e);
+                       if (t->target.u.target_size
+                           != ALIGN(sizeof(STRUCT_STANDARD_TARGET))) {
+                               errno = EINVAL;
+                               return -1;
+                       }
+
+                       if (t->verdict < 0) {
+                               DEBUGP_C("standard, verdict=%d\n", t->verdict);
+                               r->type = IPTCC_R_STANDARD;
+                       } else if (t->verdict == r->offset+e->next_offset) {
+                               DEBUGP_C("fallthrough\n");
+                               r->type = IPTCC_R_FALLTHROUGH;
+                       } else {
+                               DEBUGP_C("jump, target=%u\n", t->verdict);
+                               r->type = IPTCC_R_JUMP;
+                               /* Jump target fixup has to be deferred
+                                * until second pass, since we migh not
+                                * yet have parsed the target */
+                       }
+               } else {
+                       DEBUGP_C("module, target=%s\n", GET_TARGET(e)->u.user.name);
+                       r->type = IPTCC_R_MODULE;
+               }
+
+               list_add_tail(&r->list, &h->chain_iterator_cur->rules);
+               h->chain_iterator_cur->num_rules++;
+       }
+out_inc:
+       (*num)++;
+       return 0;
+}
+
+
+/* parse an iptables blob into it's pieces */
+static int parse_table(TC_HANDLE_T h)
+{
+       STRUCT_ENTRY *prev;
+       unsigned int num = 0;
+       struct chain_head *c;
+
+       /* First pass: over ruleset blob */
+       ENTRY_ITERATE(h->entries->entrytable, h->entries->size,
+                       cache_add_entry, h, &prev, &num);
+
+       /* Second pass: fixup parsed data from first pass */
+       list_for_each_entry(c, &h->chains, list) {
+               struct rule_head *r;
+               list_for_each_entry(r, &c->rules, list) {
+                       struct chain_head *c;
+                       STRUCT_STANDARD_TARGET *t;
+
+                       if (r->type != IPTCC_R_JUMP)
+                               continue;
+
+                       t = (STRUCT_STANDARD_TARGET *)GET_TARGET(r->entry);
+                       c = iptcc_find_chain_by_offset(h, t->verdict);
+                       if (!c)
+                               return -1;
+                       r->jump = c;
+                       c->references++;
+               }
+       }
+
+       /* FIXME: sort chains */
+
+       return 1;
+}
+
+
+/**********************************************************************
+ * RULESET COMPILATION (cache -> blob)
+ **********************************************************************/
+
+/* Convenience structures */
+struct iptcb_chain_start{
+       STRUCT_ENTRY e;
+       struct ipt_error_target name;
+};
+#define IPTCB_CHAIN_START_SIZE (sizeof(STRUCT_ENTRY) +                 \
+                                ALIGN(sizeof(struct ipt_error_target)))
+
+struct iptcb_chain_foot {
+       STRUCT_ENTRY e;
+       STRUCT_STANDARD_TARGET target;
+};
+#define IPTCB_CHAIN_FOOT_SIZE  (sizeof(STRUCT_ENTRY) +                 \
+                                ALIGN(sizeof(STRUCT_STANDARD_TARGET)))
+
+struct iptcb_chain_error {
+       STRUCT_ENTRY entry;
+       struct ipt_error_target target;
+};
+#define IPTCB_CHAIN_ERROR_SIZE (sizeof(STRUCT_ENTRY) +                 \
+                                ALIGN(sizeof(struct ipt_error_target)))
+
+
+
+/* compile rule from cache into blob */
+static inline int iptcc_compile_rule (TC_HANDLE_T h, STRUCT_REPLACE *repl, struct rule_head *r)
+{
+       /* handle jumps */
+       if (r->type == IPTCC_R_JUMP) {
+               STRUCT_STANDARD_TARGET *t;
+               t = (STRUCT_STANDARD_TARGET *)GET_TARGET(r->entry);
+               /* memset for memcmp convenience on delete/replace */
+               memset(t->target.u.user.name, 0, FUNCTION_MAXNAMELEN);
+               strcpy(t->target.u.user.name, STANDARD_TARGET);
+               /* Jumps can only happen to builtin chains, so we
+                * can safely assume that they always have a header */
+               t->verdict = r->jump->head_offset + IPTCB_CHAIN_START_SIZE;
+       } else if (r->type == IPTCC_R_FALLTHROUGH) {
+               STRUCT_STANDARD_TARGET *t;
+               t = (STRUCT_STANDARD_TARGET *)GET_TARGET(r->entry);
+               t->verdict = r->offset + r->size;
+       }
+       
+       /* copy entry from cache to blob */
+       memcpy((char *)repl->entries+r->offset, r->entry, r->size);
+
+       return 1;
+}
+
+/* compile chain from cache into blob */
+static int iptcc_compile_chain(TC_HANDLE_T h, STRUCT_REPLACE *repl, struct chain_head *c)
+{
+       int ret;
+       struct rule_head *r;
+       struct iptcb_chain_start *head;
+       struct iptcb_chain_foot *foot;
+
+       /* only user-defined chains have heaer */
+       if (!iptcc_is_builtin(c)) {
+               /* put chain header in place */
+               head = (void *)repl->entries + c->head_offset;
+               head->e.target_offset = sizeof(STRUCT_ENTRY);
+               head->e.next_offset = IPTCB_CHAIN_START_SIZE;
+               strcpy(head->name.t.u.user.name, ERROR_TARGET);
+               head->name.t.u.target_size = 
+                               ALIGN(sizeof(struct ipt_error_target));
+               strcpy(head->name.error, c->name);
+       } else {
+               repl->hook_entry[c->hooknum-1] = c->head_offset;        
+               repl->underflow[c->hooknum-1] = c->foot_offset;
+       }
+
+       /* iterate over rules */
+       list_for_each_entry(r, &c->rules, list) {
+               ret = iptcc_compile_rule(h, repl, r);
+               if (ret < 0)
+                       return ret;
+       }
+
+       /* put chain footer in place */
+       foot = (void *)repl->entries + c->foot_offset;
+       foot->e.target_offset = sizeof(STRUCT_ENTRY);
+       foot->e.next_offset = IPTCB_CHAIN_FOOT_SIZE;
+       strcpy(foot->target.target.u.user.name, STANDARD_TARGET);
+       foot->target.target.u.target_size =
+                               ALIGN(sizeof(STRUCT_STANDARD_TARGET));
+       /* builtin targets have verdict, others return */
+       if (iptcc_is_builtin(c))
+               foot->target.verdict = c->verdict;
+       else
+               foot->target.verdict = RETURN;
+       /* set policy-counters */
+       memcpy(&foot->e.counters, &c->counters, sizeof(STRUCT_COUNTERS));
+
+       return 0;
+}
+
+/* calculate offset and number for every rule in the cache */
+static int iptcc_compile_chain_offsets(TC_HANDLE_T h, struct chain_head *c,
+                                      unsigned int *offset, unsigned int *num)
+{
+       struct rule_head *r;
+
+       c->head_offset = *offset;
+       DEBUGP("%s: chain_head %u, offset=%u\n", c->name, *num, *offset);
+
+       if (!iptcc_is_builtin(c))  {
+               /* Chain has header */
+               *offset += sizeof(STRUCT_ENTRY) 
+                            + ALIGN(sizeof(struct ipt_error_target));
+               (*num)++;
+       }
+
+       list_for_each_entry(r, &c->rules, list) {
+               DEBUGP("rule %u, offset=%u, index=%u\n", *num, *offset, *num);
+               r->offset = *offset;
+               r->index = *num;
+               *offset += r->size;
+               (*num)++;
+       }
+
+       DEBUGP("%s; chain_foot %u, offset=%u, index=%u\n", c->name, *num, 
+               *offset, *num);
+       c->foot_offset = *offset;
+       c->foot_index = *num;
+       *offset += sizeof(STRUCT_ENTRY)
+                  + ALIGN(sizeof(STRUCT_STANDARD_TARGET));
+       (*num)++;
+
+       return 1;
+}
+
+/* put the pieces back together again */
+static int iptcc_compile_table_prep(TC_HANDLE_T h, unsigned int *size)
+{
+       struct chain_head *c;
+       unsigned int offset = 0, num = 0;
+       int ret = 0;
+
+       /* First pass: calculate offset for every rule */
+       list_for_each_entry(c, &h->chains, list) {
+               ret = iptcc_compile_chain_offsets(h, c, &offset, &num);
+               if (ret < 0)
+                       return ret;
+       }
+
+       /* Append one error rule at end of chain */
+       num++;
+       offset += sizeof(STRUCT_ENTRY)
+                 + ALIGN(sizeof(struct ipt_error_target));
+
+       /* ruleset size is now in offset */
+       *size = offset;
+       return num;
+}
+
+static int iptcc_compile_table(TC_HANDLE_T h, STRUCT_REPLACE *repl)
+{
+       struct chain_head *c;
+       struct iptcb_chain_error *error;
+
+       /* Second pass: copy from cache to offsets, fill in jumps */
+       list_for_each_entry(c, &h->chains, list) {
+               int ret = iptcc_compile_chain(h, repl, c);
+               if (ret < 0)
+                       return ret;
+       }
+
+       /* Append error rule at end of chain */
+       error = (void *)repl->entries + repl->size - IPTCB_CHAIN_ERROR_SIZE;
+       error->entry.target_offset = sizeof(STRUCT_ENTRY);
+       error->entry.next_offset = IPTCB_CHAIN_ERROR_SIZE;
+       error->target.t.u.user.target_size = 
+               ALIGN(sizeof(struct ipt_error_target));
+       strcpy((char *)&error->target.t.u.user.name, ERROR_TARGET);
+       strcpy((char *)&error->target.error, "ERROR");
+
+       return 1;
+}
+
+/**********************************************************************
+ * EXTERNAL API (operates on cache only)
+ **********************************************************************/
+
+/* Allocate handle of given size */
+static TC_HANDLE_T
+alloc_handle(const char *tablename, unsigned int size, unsigned int num_rules)
+{
+       size_t len;
+       TC_HANDLE_T h;
+
+       len = sizeof(STRUCT_TC_HANDLE) + size;
+
+       h = malloc(sizeof(STRUCT_TC_HANDLE));
+       if (!h) {
+               errno = ENOMEM;
+               return NULL;
+       }
+       memset(h, 0, sizeof(*h));
+       INIT_LIST_HEAD(&h->chains);
+       strcpy(h->info.name, tablename);
+
+       h->entries = malloc(sizeof(STRUCT_GET_ENTRIES) + size);
+       if (!h->entries)
+               goto out_free_handle;
+
+       strcpy(h->entries->name, tablename);
+       h->entries->size = size;
+
+       return h;
+
+out_free_handle:
+       free(h);
+
+       return NULL;
+}
+
+
+TC_HANDLE_T
+TC_INIT(const char *tablename)
+{
+       TC_HANDLE_T h;
+       STRUCT_GETINFO info;
+       unsigned int tmp;
+       socklen_t s;
+
+       iptc_fn = TC_INIT;
+
+       if (strlen(tablename) >= TABLE_MAXNAMELEN) {
+               errno = EINVAL;
+               return NULL;
+       }
+       
+       if (sockfd_use == 0) {
+               sockfd = socket(TC_AF, SOCK_RAW, IPPROTO_RAW);
+               if (sockfd < 0)
+                       return NULL;
+       }
+       sockfd_use++;
+
+       s = sizeof(info);
+
+       strcpy(info.name, tablename);
+       if (getsockopt(sockfd, TC_IPPROTO, SO_GET_INFO, &info, &s) < 0) {
+               if (--sockfd_use == 0) {
+                       close(sockfd);
+                       sockfd = -1;
+               }
+               return NULL;
+       }
+
+       DEBUGP("valid_hooks=0x%08x, num_entries=%u, size=%u\n",
+               info.valid_hooks, info.num_entries, info.size);
+
+       if ((h = alloc_handle(info.name, info.size, info.num_entries))
+           == NULL) {
+               if (--sockfd_use == 0) {
+                       close(sockfd);
+                       sockfd = -1;
+               }
+               return NULL;
+       }
+
+       /* Initialize current state */
+       h->info = info;
+
+       h->entries->size = h->info.size;
+
+       tmp = sizeof(STRUCT_GET_ENTRIES) + h->info.size;
+
+       if (getsockopt(sockfd, TC_IPPROTO, SO_GET_ENTRIES, h->entries,
+                      &tmp) < 0)
+               goto error;
+
+#ifdef IPTC_DEBUG2
+       {
+               int fd = open("/tmp/libiptc-so_get_entries.blob", 
+                               O_CREAT|O_WRONLY);
+               if (fd >= 0) {
+                       write(fd, h->entries, tmp);
+                       close(fd);
+               }
+       }
+#endif
+
+       if (parse_table(h) < 0)
+               goto error;
+
+       CHECK(h);
+       return h;
+error:
+       TC_FREE(&h);
+       return NULL;
+}
+
+void
+TC_FREE(TC_HANDLE_T *h)
+{
+       struct chain_head *c, *tmp;
+
+       iptc_fn = TC_FREE;
+       if (--sockfd_use == 0) {
+               close(sockfd);
+               sockfd = -1;
+       }
+
+       list_for_each_entry_safe(c, tmp, &(*h)->chains, list) {
+               struct rule_head *r, *rtmp;
+
+               list_for_each_entry_safe(r, rtmp, &c->rules, list) {
+                       free(r);
+               }
+
+               free(c);
+       }
+
+       free((*h)->entries);
+       free(*h);
+
+       *h = NULL;
+}
+
+static inline int
+print_match(const STRUCT_ENTRY_MATCH *m)
+{
+       printf("Match name: `%s'\n", m->u.user.name);
+       return 0;
+}
+
+static int dump_entry(STRUCT_ENTRY *e, const TC_HANDLE_T handle);
+void
+TC_DUMP_ENTRIES(const TC_HANDLE_T handle)
+{
+       iptc_fn = TC_DUMP_ENTRIES;
+       CHECK(handle);
+
+       printf("libiptc v%s. %u bytes.\n",
+              IPTABLES_VERSION, handle->entries->size);
+       printf("Table `%s'\n", handle->info.name);
+       printf("Hooks: pre/in/fwd/out/post = %u/%u/%u/%u/%u\n",
+              handle->info.hook_entry[HOOK_PRE_ROUTING],
+              handle->info.hook_entry[HOOK_LOCAL_IN],
+              handle->info.hook_entry[HOOK_FORWARD],
+              handle->info.hook_entry[HOOK_LOCAL_OUT],
+              handle->info.hook_entry[HOOK_POST_ROUTING]);
+       printf("Underflows: pre/in/fwd/out/post = %u/%u/%u/%u/%u\n",
+              handle->info.underflow[HOOK_PRE_ROUTING],
+              handle->info.underflow[HOOK_LOCAL_IN],
+              handle->info.underflow[HOOK_FORWARD],
+              handle->info.underflow[HOOK_LOCAL_OUT],
+              handle->info.underflow[HOOK_POST_ROUTING]);
+
+       ENTRY_ITERATE(handle->entries->entrytable, handle->entries->size,
+                     dump_entry, handle);
+}
+
+/* Does this chain exist? */
+int TC_IS_CHAIN(const char *chain, const TC_HANDLE_T handle)
+{
+       iptc_fn = TC_IS_CHAIN;
+       return iptcc_find_label(chain, handle) != NULL;
+}
+
+static void iptcc_chain_iterator_advance(TC_HANDLE_T handle)
+{
+       struct chain_head *c = handle->chain_iterator_cur;
+
+       if (c->list.next == &handle->chains)
+               handle->chain_iterator_cur = NULL;
+       else
+               handle->chain_iterator_cur = 
+                       list_entry(c->list.next, struct chain_head, list);
+}
+
+/* Iterator functions to run through the chains. */
+const char *
+TC_FIRST_CHAIN(TC_HANDLE_T *handle)
+{
+       struct chain_head *c = list_entry((*handle)->chains.next,
+                                         struct chain_head, list);
+
+       iptc_fn = TC_FIRST_CHAIN;
+
+
+       if (list_empty(&(*handle)->chains)) {
+               DEBUGP(": no chains\n");
+               return NULL;
+       }
+
+       (*handle)->chain_iterator_cur = c;
+       iptcc_chain_iterator_advance(*handle);
+
+       DEBUGP(": returning `%s'\n", c->name);
+       return c->name;
+}
+
+/* Iterator functions to run through the chains.  Returns NULL at end. */
+const char *
+TC_NEXT_CHAIN(TC_HANDLE_T *handle)
+{
+       struct chain_head *c = (*handle)->chain_iterator_cur;
+
+       iptc_fn = TC_NEXT_CHAIN;
+
+       if (!c) {
+               DEBUGP(": no more chains\n");
+               return NULL;
+       }
+
+       iptcc_chain_iterator_advance(*handle);
+       
+       DEBUGP(": returning `%s'\n", c->name);
+       return c->name;
+}
+
+/* Get first rule in the given chain: NULL for empty chain. */
+const STRUCT_ENTRY *
+TC_FIRST_RULE(const char *chain, TC_HANDLE_T *handle)
+{
+       struct chain_head *c;
+       struct rule_head *r;
+
+       iptc_fn = TC_FIRST_RULE;
+
+       DEBUGP("first rule(%s): ", chain);
+
+       c = iptcc_find_label(chain, *handle);
+       if (!c) {
+               errno = ENOENT;
+               return NULL;
+       }
+
+       /* Empty chain: single return/policy rule */
+       if (list_empty(&c->rules)) {
+               DEBUGP_C("no rules, returning NULL\n");
+               return NULL;
+       }
+
+       r = list_entry(c->rules.next, struct rule_head, list);
+       (*handle)->rule_iterator_cur = r;
+       DEBUGP_C("%p\n", r);
+
+       return r->entry;
+}
+
+/* Returns NULL when rules run out. */
+const STRUCT_ENTRY *
+TC_NEXT_RULE(const STRUCT_ENTRY *prev, TC_HANDLE_T *handle)
+{
+       struct rule_head *r;
+
+       iptc_fn = TC_NEXT_RULE;
+       DEBUGP("rule_iterator_cur=%p...", (*handle)->rule_iterator_cur);
+
+       if (!(*handle)->rule_iterator_cur) {
+               DEBUGP_C("returning NULL\n");
+               return NULL;
+       }
+       
+       r = list_entry((*handle)->rule_iterator_cur->list.next, 
+                       struct rule_head, list);
+
+       iptc_fn = TC_NEXT_RULE;
+
+       DEBUGP_C("next=%p, head=%p...", &r->list, 
+               &(*handle)->rule_iterator_cur->chain->rules);
+
+       if (&r->list == &(*handle)->rule_iterator_cur->chain->rules) {
+               (*handle)->rule_iterator_cur = NULL;
+               DEBUGP_C("finished, returning NULL\n");
+               return NULL;
+       }
+
+       (*handle)->rule_iterator_cur = r;
+
+       /* NOTE: prev is without any influence ! */
+       DEBUGP_C("returning rule %p\n", r);
+       return r->entry;
+}
+
+/* How many rules in this chain? */
+unsigned int
+TC_NUM_RULES(const char *chain, TC_HANDLE_T *handle)
+{
+       struct chain_head *c;
+       iptc_fn = TC_NUM_RULES;
+       CHECK(*handle);
+
+       c = iptcc_find_label(chain, *handle);
+       if (!c) {
+               errno = ENOENT;
+               return (unsigned int)-1;
+       }
+       
+       return c->num_rules;
+}
+
+const STRUCT_ENTRY *TC_GET_RULE(const char *chain,
+                               unsigned int n,
+                               TC_HANDLE_T *handle)
+{
+       struct chain_head *c;
+       struct rule_head *r;
+       
+       iptc_fn = TC_GET_RULE;
+
+       CHECK(*handle);
+
+       c = iptcc_find_label(chain, *handle);
+       if (!c) {
+               errno = ENOENT;
+               return NULL;
+       }
+
+       r = iptcc_get_rule_num(c, n);
+       if (!r)
+               return NULL;
+       return r->entry;
+}
+
+/* Returns a pointer to the target name of this position. */
+static const char *standard_target_map(int verdict)
+{
+       switch (verdict) {
+               case RETURN:
+                       return LABEL_RETURN;
+                       break;
+               case -NF_ACCEPT-1:
+                       return LABEL_ACCEPT;
+                       break;
+               case -NF_DROP-1:
+                       return LABEL_DROP;
+                       break;
+               case -NF_QUEUE-1:
+                       return LABEL_QUEUE;
+                       break;
+               default:
+                       fprintf(stderr, "ERROR: %d not a valid target)\n",
+                               verdict);
+                       abort();
+                       break;
+       }
+       /* not reached */
+       return NULL;
+}
+
+/* Returns a pointer to the target name of this position. */
+const char *TC_GET_TARGET(const STRUCT_ENTRY *ce,
+                         TC_HANDLE_T *handle)
+{
+       STRUCT_ENTRY *e = (STRUCT_ENTRY *)ce;
+       struct rule_head *r = container_of(e, struct rule_head, entry[0]);
+
+       iptc_fn = TC_GET_TARGET;
+
+       switch(r->type) {
+               int spos;
+               case IPTCC_R_FALLTHROUGH:
+                       return "";
+                       break;
+               case IPTCC_R_JUMP:
+                       DEBUGP("r=%p, jump=%p, name=`%s'\n", r, r->jump, r->jump->name);
+                       return r->jump->name;
+                       break;
+               case IPTCC_R_STANDARD:
+                       spos = *(int *)GET_TARGET(e)->data;
+                       DEBUGP("r=%p, spos=%d'\n", r, spos);
+                       return standard_target_map(spos);
+                       break;
+               case IPTCC_R_MODULE:
+                       return GET_TARGET(e)->u.user.name;
+                       break;
+       }
+       return NULL;
+}
+/* Is this a built-in chain?  Actually returns hook + 1. */
+int
+TC_BUILTIN(const char *chain, const TC_HANDLE_T handle)
+{
+       struct chain_head *c;
+       
+       iptc_fn = TC_BUILTIN;
+
+       c = iptcc_find_label(chain, handle);
+       if (!c) {
+               errno = ENOENT;
+               return 0;
+       }
+
+       return iptcc_is_builtin(c);
+}
+
+/* Get the policy of a given built-in chain */
+const char *
+TC_GET_POLICY(const char *chain,
+             STRUCT_COUNTERS *counters,
+             TC_HANDLE_T *handle)
+{
+       struct chain_head *c;
+
+       iptc_fn = TC_GET_POLICY;
+
+       DEBUGP("called for chain %s\n", chain);
+
+       c = iptcc_find_label(chain, *handle);
+       if (!c) {
+               errno = ENOENT;
+               return NULL;
+       }
+
+       if (!iptcc_is_builtin(c))
+               return NULL;
+
+       *counters = c->counters;
+
+       return standard_target_map(c->verdict);
+}
+
+static int
+iptcc_standard_map(struct rule_head *r, int verdict)
+{
+       STRUCT_ENTRY *e = r->entry;
+       STRUCT_STANDARD_TARGET *t;
+
+       t = (STRUCT_STANDARD_TARGET *)GET_TARGET(e);
+
+       if (t->target.u.target_size
+           != ALIGN(sizeof(STRUCT_STANDARD_TARGET))) {
+               errno = EINVAL;
+               return 0;
+       }
+       /* memset for memcmp convenience on delete/replace */
+       memset(t->target.u.user.name, 0, FUNCTION_MAXNAMELEN);
+       strcpy(t->target.u.user.name, STANDARD_TARGET);
+       t->verdict = verdict;
+
+       r->type = IPTCC_R_STANDARD;
+
+       return 1;
+}
+
+static int
+iptcc_map_target(const TC_HANDLE_T handle,
+          struct rule_head *r)
+{
+       STRUCT_ENTRY *e = r->entry;
+       STRUCT_ENTRY_TARGET *t = GET_TARGET(e);
+
+       /* Maybe it's empty (=> fall through) */
+       if (strcmp(t->u.user.name, "") == 0) {
+               r->type = IPTCC_R_FALLTHROUGH;
+               return 1;
+       }
+       /* Maybe it's a standard target name... */
+       else if (strcmp(t->u.user.name, LABEL_ACCEPT) == 0)
+               return iptcc_standard_map(r, -NF_ACCEPT - 1);
+       else if (strcmp(t->u.user.name, LABEL_DROP) == 0)
+               return iptcc_standard_map(r, -NF_DROP - 1);
+       else if (strcmp(t->u.user.name, LABEL_QUEUE) == 0)
+               return iptcc_standard_map(r, -NF_QUEUE - 1);
+       else if (strcmp(t->u.user.name, LABEL_RETURN) == 0)
+               return iptcc_standard_map(r, RETURN);
+       else if (TC_BUILTIN(t->u.user.name, handle)) {
+               /* Can't jump to builtins. */
+               errno = EINVAL;
+               return 0;
+       } else {
+               /* Maybe it's an existing chain name. */
+               struct chain_head *c;
+               DEBUGP("trying to find chain `%s': ", t->u.user.name);
+
+               c = iptcc_find_label(t->u.user.name, handle);
+               if (c) {
+                       DEBUGP_C("found!\n");
+                       r->type = IPTCC_R_JUMP;
+                       r->jump = c;
+                       c->references++;
+                       return 1;
+               }
+               DEBUGP_C("not found :(\n");
+       }
+
+       /* Must be a module?  If not, kernel will reject... */
+       /* memset to all 0 for your memcmp convenience: don't clear version */
+       memset(t->u.user.name + strlen(t->u.user.name),
+              0,
+              FUNCTION_MAXNAMELEN - 1 - strlen(t->u.user.name));
+       r->type = IPTCC_R_MODULE;
+       set_changed(handle);
+       return 1;
+}
+
+/* Insert the entry `fw' in chain `chain' into position `rulenum'. */
+int
+TC_INSERT_ENTRY(const IPT_CHAINLABEL chain,
+               const STRUCT_ENTRY *e,
+               unsigned int rulenum,
+               TC_HANDLE_T *handle)
+{
+       struct chain_head *c;
+       struct rule_head *r;
+       struct list_head *prev;
+
+       iptc_fn = TC_INSERT_ENTRY;
+
+       if (!(c = iptcc_find_label(chain, *handle))) {
+               errno = ENOENT;
+               return 0;
+       }
+
+       /* first rulenum index = 0
+          first c->num_rules index = 1 */
+       if (rulenum > c->num_rules) {
+               errno = E2BIG;
+               return 0;
+       }
+
+       /* If we are inserting at the end just take advantage of the
+          double linked list, insert will happen before the entry
+          prev points to. */
+       if (rulenum == c->num_rules) {
+               prev = &c->rules;
+       } else if (rulenum + 1 <= c->num_rules/2) {
+               r = iptcc_get_rule_num(c, rulenum + 1);
+               prev = &r->list;
+       } else {
+               r = iptcc_get_rule_num_reverse(c, c->num_rules - rulenum);
+               prev = &r->list;
+       }
+
+       if (!(r = iptcc_alloc_rule(c, e->next_offset))) {
+               errno = ENOMEM;
+               return 0;
+       }
+
+       memcpy(r->entry, e, e->next_offset);
+       r->counter_map.maptype = COUNTER_MAP_SET;
+
+       if (!iptcc_map_target(*handle, r)) {
+               free(r);
+               return 0;
+       }
+
+       list_add_tail(&r->list, prev);
+       c->num_rules++;
+
+       set_changed(*handle);
+
+       return 1;
+}
+
+/* Atomically replace rule `rulenum' in `chain' with `fw'. */
+int
+TC_REPLACE_ENTRY(const IPT_CHAINLABEL chain,
+                const STRUCT_ENTRY *e,
+                unsigned int rulenum,
+                TC_HANDLE_T *handle)
+{
+       struct chain_head *c;
+       struct rule_head *r, *old;
+
+       iptc_fn = TC_REPLACE_ENTRY;
+
+       if (!(c = iptcc_find_label(chain, *handle))) {
+               errno = ENOENT;
+               return 0;
+       }
+
+       if (rulenum >= c->num_rules) {
+               errno = E2BIG;
+               return 0;
+       }
+
+       /* Take advantage of the double linked list if possible. */
+       if (rulenum + 1 <= c->num_rules/2) {
+               old = iptcc_get_rule_num(c, rulenum + 1);
+       } else {
+               old = iptcc_get_rule_num_reverse(c, c->num_rules - rulenum);
+       }
+
+       if (!(r = iptcc_alloc_rule(c, e->next_offset))) {
+               errno = ENOMEM;
+               return 0;
+       }
+
+       memcpy(r->entry, e, e->next_offset);
+       r->counter_map.maptype = COUNTER_MAP_SET;
+
+       if (!iptcc_map_target(*handle, r)) {
+               free(r);
+               return 0;
+       }
+
+       list_add(&r->list, &old->list);
+       iptcc_delete_rule(old);
+
+       set_changed(*handle);
+
+       return 1;
+}
+
+/* Append entry `fw' to chain `chain'.  Equivalent to insert with
+   rulenum = length of chain. */
+int
+TC_APPEND_ENTRY(const IPT_CHAINLABEL chain,
+               const STRUCT_ENTRY *e,
+               TC_HANDLE_T *handle)
+{
+       struct chain_head *c;
+       struct rule_head *r;
+
+       iptc_fn = TC_APPEND_ENTRY;
+       if (!(c = iptcc_find_label(chain, *handle))) {
+               DEBUGP("unable to find chain `%s'\n", chain);
+               errno = ENOENT;
+               return 0;
+       }
+
+       if (!(r = iptcc_alloc_rule(c, e->next_offset))) {
+               DEBUGP("unable to allocate rule for chain `%s'\n", chain);
+               errno = ENOMEM;
+               return 0;
+       }
+
+       memcpy(r->entry, e, e->next_offset);
+       r->counter_map.maptype = COUNTER_MAP_SET;
+
+       if (!iptcc_map_target(*handle, r)) {
+               DEBUGP("unable to map target of rule for chain `%s'\n", chain);
+               free(r);
+               return 0;
+       }
+
+       list_add_tail(&r->list, &c->rules);
+       c->num_rules++;
+
+       set_changed(*handle);
+
+       return 1;
+}
+
+static inline int
+match_different(const STRUCT_ENTRY_MATCH *a,
+               const unsigned char *a_elems,
+               const unsigned char *b_elems,
+               unsigned char **maskptr)
+{
+       const STRUCT_ENTRY_MATCH *b;
+       unsigned int i;
+
+       /* Offset of b is the same as a. */
+       b = (void *)b_elems + ((unsigned char *)a - a_elems);
+
+       if (a->u.match_size != b->u.match_size)
+               return 1;
+
+       if (strcmp(a->u.user.name, b->u.user.name) != 0)
+               return 1;
+
+       *maskptr += ALIGN(sizeof(*a));
+
+       for (i = 0; i < a->u.match_size - ALIGN(sizeof(*a)); i++)
+               if (((a->data[i] ^ b->data[i]) & (*maskptr)[i]) != 0)
+                       return 1;
+       *maskptr += i;
+       return 0;
+}
+
+static inline int
+target_same(struct rule_head *a, struct rule_head *b,const unsigned char *mask)
+{
+       unsigned int i;
+       STRUCT_ENTRY_TARGET *ta, *tb;
+
+       if (a->type != b->type)
+               return 0;
+
+       ta = GET_TARGET(a->entry);
+       tb = GET_TARGET(b->entry);
+
+       switch (a->type) {
+       case IPTCC_R_FALLTHROUGH:
+               return 1;
+       case IPTCC_R_JUMP:
+               return a->jump == b->jump;
+       case IPTCC_R_STANDARD:
+               return ((STRUCT_STANDARD_TARGET *)ta)->verdict
+                       == ((STRUCT_STANDARD_TARGET *)tb)->verdict;
+       case IPTCC_R_MODULE:
+               if (ta->u.target_size != tb->u.target_size)
+                       return 0;
+               if (strcmp(ta->u.user.name, tb->u.user.name) != 0)
+                       return 0;
+
+               for (i = 0; i < ta->u.target_size - sizeof(*ta); i++)
+                       if (((ta->data[i] ^ tb->data[i]) & mask[i]) != 0)
+                               return 0;
+               return 1;
+       default:
+               fprintf(stderr, "ERROR: bad type %i\n", a->type);
+               abort();
+       }
+}
+
+static unsigned char *
+is_same(const STRUCT_ENTRY *a,
+       const STRUCT_ENTRY *b,
+       unsigned char *matchmask);
+
+/* Delete the first rule in `chain' which matches `fw'. */
+int
+TC_DELETE_ENTRY(const IPT_CHAINLABEL chain,
+               const STRUCT_ENTRY *origfw,
+               unsigned char *matchmask,
+               TC_HANDLE_T *handle)
+{
+       struct chain_head *c;
+       struct rule_head *r, *i;
+
+       iptc_fn = TC_DELETE_ENTRY;
+       if (!(c = iptcc_find_label(chain, *handle))) {
+               errno = ENOENT;
+               return 0;
+       }
+
+       /* Create a rule_head from origfw. */
+       r = iptcc_alloc_rule(c, origfw->next_offset);
+       if (!r) {
+               errno = ENOMEM;
+               return 0;
+       }
+
+       memcpy(r->entry, origfw, origfw->next_offset);
+       r->counter_map.maptype = COUNTER_MAP_NOMAP;
+       if (!iptcc_map_target(*handle, r)) {
+               DEBUGP("unable to map target of rule for chain `%s'\n", chain);
+               free(r);
+               return 0;
+       } else {
+               /* iptcc_map_target increment target chain references
+                * since this is a fake rule only used for matching
+                * the chain references count is decremented again. 
+                */
+               if (r->type == IPTCC_R_JUMP
+                   && r->jump)
+                       r->jump->references--;
+       }
+
+       list_for_each_entry(i, &c->rules, list) {
+               unsigned char *mask;
+
+               mask = is_same(r->entry, i->entry, matchmask);
+               if (!mask)
+                       continue;
+
+               if (!target_same(r, i, mask))
+                       continue;
+
+               /* If we are about to delete the rule that is the
+                * current iterator, move rule iterator back.  next
+                * pointer will then point to real next node */
+               if (i == (*handle)->rule_iterator_cur) {
+                       (*handle)->rule_iterator_cur = 
+                               list_entry((*handle)->rule_iterator_cur->list.prev,
+                                          struct rule_head, list);
+               }
+
+               c->num_rules--;
+               iptcc_delete_rule(i);
+
+               set_changed(*handle);
+               free(r);
+               return 1;
+       }
+
+       free(r);
+       errno = ENOENT;
+       return 0;
+}
+
+
+/* Delete the rule in position `rulenum' in `chain'. */
+int
+TC_DELETE_NUM_ENTRY(const IPT_CHAINLABEL chain,
+                   unsigned int rulenum,
+                   TC_HANDLE_T *handle)
+{
+       struct chain_head *c;
+       struct rule_head *r;
+
+       iptc_fn = TC_DELETE_NUM_ENTRY;
+
+       if (!(c = iptcc_find_label(chain, *handle))) {
+               errno = ENOENT;
+               return 0;
+       }
+
+       if (rulenum >= c->num_rules) {
+               errno = E2BIG;
+               return 0;
+       }
+
+       /* Take advantage of the double linked list if possible. */
+       if (rulenum + 1 <= c->num_rules/2) {
+               r = iptcc_get_rule_num(c, rulenum + 1);
+       } else {
+               r = iptcc_get_rule_num_reverse(c, c->num_rules - rulenum);
+       }
+
+       /* If we are about to delete the rule that is the current
+        * iterator, move rule iterator back.  next pointer will then
+        * point to real next node */
+       if (r == (*handle)->rule_iterator_cur) {
+               (*handle)->rule_iterator_cur = 
+                       list_entry((*handle)->rule_iterator_cur->list.prev,
+                                  struct rule_head, list);
+       }
+
+       c->num_rules--;
+       iptcc_delete_rule(r);
+
+       set_changed(*handle);
+
+       return 1;
+}
+
+/* Check the packet `fw' on chain `chain'.  Returns the verdict, or
+   NULL and sets errno. */
+const char *
+TC_CHECK_PACKET(const IPT_CHAINLABEL chain,
+               STRUCT_ENTRY *entry,
+               TC_HANDLE_T *handle)
+{
+       iptc_fn = TC_CHECK_PACKET;
+       errno = ENOSYS;
+       return NULL;
+}
+
+/* Flushes the entries in the given chain (ie. empties chain). */
+int
+TC_FLUSH_ENTRIES(const IPT_CHAINLABEL chain, TC_HANDLE_T *handle)
+{
+       struct chain_head *c;
+       struct rule_head *r, *tmp;
+
+       iptc_fn = TC_FLUSH_ENTRIES;
+       if (!(c = iptcc_find_label(chain, *handle))) {
+               errno = ENOENT;
+               return 0;
+       }
+
+       list_for_each_entry_safe(r, tmp, &c->rules, list) {
+               iptcc_delete_rule(r);
+       }
+
+       c->num_rules = 0;
+
+       set_changed(*handle);
+
+       return 1;
+}
+
+/* Zeroes the counters in a chain. */
+int
+TC_ZERO_ENTRIES(const IPT_CHAINLABEL chain, TC_HANDLE_T *handle)
+{
+       struct chain_head *c;
+       struct rule_head *r;
+
+       iptc_fn = TC_ZERO_ENTRIES;
+       if (!(c = iptcc_find_label(chain, *handle))) {
+               errno = ENOENT;
+               return 0;
+       }
+
+       if (c->counter_map.maptype == COUNTER_MAP_NORMAL_MAP)
+               c->counter_map.maptype = COUNTER_MAP_ZEROED;
+
+       list_for_each_entry(r, &c->rules, list) {
+               if (r->counter_map.maptype == COUNTER_MAP_NORMAL_MAP)
+                       r->counter_map.maptype = COUNTER_MAP_ZEROED;
+       }
+
+       set_changed(*handle);
+
+       return 1;
+}
+
+STRUCT_COUNTERS *
+TC_READ_COUNTER(const IPT_CHAINLABEL chain,
+               unsigned int rulenum,
+               TC_HANDLE_T *handle)
+{
+       struct chain_head *c;
+       struct rule_head *r;
+
+       iptc_fn = TC_READ_COUNTER;
+       CHECK(*handle);
+
+       if (!(c = iptcc_find_label(chain, *handle))) {
+               errno = ENOENT;
+               return NULL;
+       }
+
+       if (!(r = iptcc_get_rule_num(c, rulenum))) {
+               errno = E2BIG;
+               return NULL;
+       }
+
+       return &r->entry[0].counters;
+}
+
+int
+TC_ZERO_COUNTER(const IPT_CHAINLABEL chain,
+               unsigned int rulenum,
+               TC_HANDLE_T *handle)
+{
+       struct chain_head *c;
+       struct rule_head *r;
+       
+       iptc_fn = TC_ZERO_COUNTER;
+       CHECK(*handle);
+
+       if (!(c = iptcc_find_label(chain, *handle))) {
+               errno = ENOENT;
+               return 0;
+       }
+
+       if (!(r = iptcc_get_rule_num(c, rulenum))) {
+               errno = E2BIG;
+               return 0;
+       }
+
+       if (r->counter_map.maptype == COUNTER_MAP_NORMAL_MAP)
+               r->counter_map.maptype = COUNTER_MAP_ZEROED;
+
+       set_changed(*handle);
+
+       return 1;
+}
+
+int 
+TC_SET_COUNTER(const IPT_CHAINLABEL chain,
+              unsigned int rulenum,
+              STRUCT_COUNTERS *counters,
+              TC_HANDLE_T *handle)
+{
+       struct chain_head *c;
+       struct rule_head *r;
+       STRUCT_ENTRY *e;
+
+       iptc_fn = TC_SET_COUNTER;
+       CHECK(*handle);
+
+       if (!(c = iptcc_find_label(chain, *handle))) {
+               errno = ENOENT;
+               return 0;
+       }
+
+       if (!(r = iptcc_get_rule_num(c, rulenum))) {
+               errno = E2BIG;
+               return 0;
+       }
+
+       e = r->entry;
+       r->counter_map.maptype = COUNTER_MAP_SET;
+
+       memcpy(&e->counters, counters, sizeof(STRUCT_COUNTERS));
+
+       set_changed(*handle);
+
+       return 1;
+}
+
+/* Creates a new chain. */
+/* To create a chain, create two rules: error node and unconditional
+ * return. */
+int
+TC_CREATE_CHAIN(const IPT_CHAINLABEL chain, TC_HANDLE_T *handle)
+{
+       static struct chain_head *c;
+
+       iptc_fn = TC_CREATE_CHAIN;
+
+       /* find_label doesn't cover built-in targets: DROP, ACCEPT,
+           QUEUE, RETURN. */
+       if (iptcc_find_label(chain, *handle)
+           || strcmp(chain, LABEL_DROP) == 0
+           || strcmp(chain, LABEL_ACCEPT) == 0
+           || strcmp(chain, LABEL_QUEUE) == 0
+           || strcmp(chain, LABEL_RETURN) == 0) {
+               DEBUGP("Chain `%s' already exists\n", chain);
+               errno = EEXIST;
+               return 0;
+       }
+
+       if (strlen(chain)+1 > sizeof(IPT_CHAINLABEL)) {
+               DEBUGP("Chain name `%s' too long\n", chain);
+               errno = EINVAL;
+               return 0;
+       }
+
+       c = iptcc_alloc_chain_head(chain, 0);
+       if (!c) {
+               DEBUGP("Cannot allocate memory for chain `%s'\n", chain);
+               errno = ENOMEM;
+               return 0;
+
+       }
+
+       DEBUGP("Creating chain `%s'\n", chain);
+       iptc_insert_chain(*handle, c); /* Insert sorted */
+
+       set_changed(*handle);
+
+       return 1;
+}
+
+/* Get the number of references to this chain. */
+int
+TC_GET_REFERENCES(unsigned int *ref, const IPT_CHAINLABEL chain,
+                 TC_HANDLE_T *handle)
+{
+       struct chain_head *c;
+
+       iptc_fn = TC_GET_REFERENCES;
+       if (!(c = iptcc_find_label(chain, *handle))) {
+               errno = ENOENT;
+               return 0;
+       }
+
+       *ref = c->references;
+
+       return 1;
+}
+
+/* Deletes a chain. */
+int
+TC_DELETE_CHAIN(const IPT_CHAINLABEL chain, TC_HANDLE_T *handle)
+{
+       unsigned int references;
+       struct chain_head *c;
+
+       iptc_fn = TC_DELETE_CHAIN;
+
+       if (!(c = iptcc_find_label(chain, *handle))) {
+               DEBUGP("cannot find chain `%s'\n", chain);
+               errno = ENOENT;
+               return 0;
+       }
+
+       if (TC_BUILTIN(chain, *handle)) {
+               DEBUGP("cannot remove builtin chain `%s'\n", chain);
+               errno = EINVAL;
+               return 0;
+       }
+
+       if (!TC_GET_REFERENCES(&references, chain, handle)) {
+               DEBUGP("cannot get references on chain `%s'\n", chain);
+               return 0;
+       }
+
+       if (references > 0) {
+               DEBUGP("chain `%s' still has references\n", chain);
+               errno = EMLINK;
+               return 0;
+       }
+
+       if (c->num_rules) {
+               DEBUGP("chain `%s' is not empty\n", chain);
+               errno = ENOTEMPTY;
+               return 0;
+       }
+
+       /* If we are about to delete the chain that is the current
+        * iterator, move chain iterator firward. */
+       if (c == (*handle)->chain_iterator_cur)
+               iptcc_chain_iterator_advance(*handle);
+
+       list_del(&c->list);
+       free(c);
+
+       DEBUGP("chain `%s' deleted\n", chain);
+
+       set_changed(*handle);
+
+       return 1;
+}
+
+/* Renames a chain. */
+int TC_RENAME_CHAIN(const IPT_CHAINLABEL oldname,
+                   const IPT_CHAINLABEL newname,
+                   TC_HANDLE_T *handle)
+{
+       struct chain_head *c;
+       iptc_fn = TC_RENAME_CHAIN;
+
+       /* find_label doesn't cover built-in targets: DROP, ACCEPT,
+           QUEUE, RETURN. */
+       if (iptcc_find_label(newname, *handle)
+           || strcmp(newname, LABEL_DROP) == 0
+           || strcmp(newname, LABEL_ACCEPT) == 0
+           || strcmp(newname, LABEL_QUEUE) == 0
+           || strcmp(newname, LABEL_RETURN) == 0) {
+               errno = EEXIST;
+               return 0;
+       }
+
+       if (!(c = iptcc_find_label(oldname, *handle))
+           || TC_BUILTIN(oldname, *handle)) {
+               errno = ENOENT;
+               return 0;
+       }
+
+       if (strlen(newname)+1 > sizeof(IPT_CHAINLABEL)) {
+               errno = EINVAL;
+               return 0;
+       }
+
+       strncpy(c->name, newname, sizeof(IPT_CHAINLABEL));
+       
+       set_changed(*handle);
+
+       return 1;
+}
+
+/* Sets the policy on a built-in chain. */
+int
+TC_SET_POLICY(const IPT_CHAINLABEL chain,
+             const IPT_CHAINLABEL policy,
+             STRUCT_COUNTERS *counters,
+             TC_HANDLE_T *handle)
+{
+       struct chain_head *c;
+
+       iptc_fn = TC_SET_POLICY;
+
+       if (!(c = iptcc_find_label(chain, *handle))) {
+               DEBUGP("cannot find chain `%s'\n", chain);
+               errno = ENOENT;
+               return 0;
+       }
+
+       if (!iptcc_is_builtin(c)) {
+               DEBUGP("cannot set policy of userdefinedchain `%s'\n", chain);
+               errno = ENOENT;
+               return 0;
+       }
+
+       if (strcmp(policy, LABEL_ACCEPT) == 0)
+               c->verdict = -NF_ACCEPT - 1;
+       else if (strcmp(policy, LABEL_DROP) == 0)
+               c->verdict = -NF_DROP - 1;
+       else {
+               errno = EINVAL;
+               return 0;
+       }
+
+       if (counters) {
+               /* set byte and packet counters */
+               memcpy(&c->counters, counters, sizeof(STRUCT_COUNTERS));
+               c->counter_map.maptype = COUNTER_MAP_SET;
+       } else {
+               c->counter_map.maptype = COUNTER_MAP_NOMAP;
+       }
+
+       set_changed(*handle);
+
+       return 1;
+}
+
+/* Without this, on gcc 2.7.2.3, we get:
+   libiptc.c: In function `TC_COMMIT':
+   libiptc.c:833: fixed or forbidden register was spilled.
+   This may be due to a compiler bug or to impossible asm
+   statements or clauses.
+*/
+static void
+subtract_counters(STRUCT_COUNTERS *answer,
+                 const STRUCT_COUNTERS *a,
+                 const STRUCT_COUNTERS *b)
+{
+       answer->pcnt = a->pcnt - b->pcnt;
+       answer->bcnt = a->bcnt - b->bcnt;
+}
+
+
+static void counters_nomap(STRUCT_COUNTERS_INFO *newcounters,
+                          unsigned int index)
+{
+       newcounters->counters[index] = ((STRUCT_COUNTERS) { 0, 0});
+       DEBUGP_C("NOMAP => zero\n");
+}
+
+static void counters_normal_map(STRUCT_COUNTERS_INFO *newcounters,
+                               STRUCT_REPLACE *repl,
+                               unsigned int index,
+                               unsigned int mappos)
+{
+       /* Original read: X.
+        * Atomic read on replacement: X + Y.
+        * Currently in kernel: Z.
+        * Want in kernel: X + Y + Z.
+        * => Add in X + Y
+        * => Add in replacement read.
+        */
+       newcounters->counters[index] = repl->counters[mappos];
+       DEBUGP_C("NORMAL_MAP => mappos %u \n", mappos);
+}
+
+static void counters_map_zeroed(STRUCT_COUNTERS_INFO *newcounters,
+                               STRUCT_REPLACE *repl,
+                               unsigned int index,
+                               unsigned int mappos,
+                               STRUCT_COUNTERS *counters)
+{
+       /* Original read: X.
+        * Atomic read on replacement: X + Y.
+        * Currently in kernel: Z.
+        * Want in kernel: Y + Z.
+        * => Add in Y.
+        * => Add in (replacement read - original read).
+        */
+       subtract_counters(&newcounters->counters[index],
+                         &repl->counters[mappos],
+                         counters);
+       DEBUGP_C("ZEROED => mappos %u\n", mappos);
+}
+
+static void counters_map_set(STRUCT_COUNTERS_INFO *newcounters,
+                            unsigned int index,
+                            STRUCT_COUNTERS *counters)
+{
+       /* Want to set counter (iptables-restore) */
+
+       memcpy(&newcounters->counters[index], counters,
+               sizeof(STRUCT_COUNTERS));
+
+       DEBUGP_C("SET\n");
+}
+
+
+int
+TC_COMMIT(TC_HANDLE_T *handle)
+{
+       /* Replace, then map back the counters. */
+       STRUCT_REPLACE *repl;
+       STRUCT_COUNTERS_INFO *newcounters;
+       struct chain_head *c;
+       int ret;
+       size_t counterlen;
+       int new_number;
+       unsigned int new_size;
+
+       iptc_fn = TC_COMMIT;
+       CHECK(*handle);
+
+       /* Don't commit if nothing changed. */
+       if (!(*handle)->changed)
+               goto finished;
+
+       new_number = iptcc_compile_table_prep(*handle, &new_size);
+       if (new_number < 0) {
+               errno = ENOMEM;
+               goto out_zero;
+       }
+
+       repl = malloc(sizeof(*repl) + new_size);
+       if (!repl) {
+               errno = ENOMEM;
+               goto out_zero;
+       }
+       memset(repl, 0, sizeof(*repl) + new_size);
+
+#if 0
+       TC_DUMP_ENTRIES(*handle);
+#endif
+
+       counterlen = sizeof(STRUCT_COUNTERS_INFO)
+                       + sizeof(STRUCT_COUNTERS) * new_number;
+
+       /* These are the old counters we will get from kernel */
+       repl->counters = malloc(sizeof(STRUCT_COUNTERS)
+                               * (*handle)->info.num_entries);
+       if (!repl->counters) {
+               errno = ENOMEM;
+               goto out_free_repl;
+       }
+       /* These are the counters we're going to put back, later. */
+       newcounters = malloc(counterlen);
+       if (!newcounters) {
+               errno = ENOMEM;
+               goto out_free_repl_counters;
+       }
+       memset(newcounters, 0, counterlen);
+
+       strcpy(repl->name, (*handle)->info.name);
+       repl->num_entries = new_number;
+       repl->size = new_size;
+
+       repl->num_counters = (*handle)->info.num_entries;
+       repl->valid_hooks = (*handle)->info.valid_hooks;
+
+       DEBUGP("num_entries=%u, size=%u, num_counters=%u\n",
+               repl->num_entries, repl->size, repl->num_counters);
+
+       ret = iptcc_compile_table(*handle, repl);
+       if (ret < 0) {
+               errno = ret;
+               goto out_free_newcounters;
+       }
+
+
+#ifdef IPTC_DEBUG2
+       {
+               int fd = open("/tmp/libiptc-so_set_replace.blob", 
+                               O_CREAT|O_WRONLY);
+               if (fd >= 0) {
+                       write(fd, repl, sizeof(*repl) + repl->size);
+                       close(fd);
+               }
+       }
+#endif
+
+       ret = setsockopt(sockfd, TC_IPPROTO, SO_SET_REPLACE, repl,
+                        sizeof(*repl) + repl->size);
+       if (ret < 0)
+               goto out_free_newcounters;
+
+       /* Put counters back. */
+       strcpy(newcounters->name, (*handle)->info.name);
+       newcounters->num_counters = new_number;
+
+       list_for_each_entry(c, &(*handle)->chains, list) {
+               struct rule_head *r;
+
+               /* Builtin chains have their own counters */
+               if (iptcc_is_builtin(c)) {
+                       DEBUGP("counter for chain-index %u: ", c->foot_index);
+                       switch(c->counter_map.maptype) {
+                       case COUNTER_MAP_NOMAP:
+                               counters_nomap(newcounters, c->foot_index);
+                               break;
+                       case COUNTER_MAP_NORMAL_MAP:
+                               counters_normal_map(newcounters, repl,
+                                                   c->foot_index, 
+                                                   c->counter_map.mappos);
+                               break;
+                       case COUNTER_MAP_ZEROED:
+                               counters_map_zeroed(newcounters, repl,
+                                                   c->foot_index, 
+                                                   c->counter_map.mappos,
+                                                   &c->counters);
+                               break;
+                       case COUNTER_MAP_SET:
+                               counters_map_set(newcounters, c->foot_index,
+                                                &c->counters);
+                               break;
+                       }
+               }
+
+               list_for_each_entry(r, &c->rules, list) {
+                       DEBUGP("counter for index %u: ", r->index);
+                       switch (r->counter_map.maptype) {
+                       case COUNTER_MAP_NOMAP:
+                               counters_nomap(newcounters, r->index);
+                               break;
+
+                       case COUNTER_MAP_NORMAL_MAP:
+                               counters_normal_map(newcounters, repl,
+                                                   r->index, 
+                                                   r->counter_map.mappos);
+                               break;
+
+                       case COUNTER_MAP_ZEROED:
+                               counters_map_zeroed(newcounters, repl,
+                                                   r->index,
+                                                   r->counter_map.mappos,
+                                                   &r->entry->counters);
+                               break;
+
+                       case COUNTER_MAP_SET:
+                               counters_map_set(newcounters, r->index,
+                                                &r->entry->counters);
+                               break;
+                       }
+               }
+       }
+
+#ifdef IPTC_DEBUG2
+       {
+               int fd = open("/tmp/libiptc-so_set_add_counters.blob", 
+                               O_CREAT|O_WRONLY);
+               if (fd >= 0) {
+                       write(fd, newcounters, counterlen);
+                       close(fd);
+               }
+       }
+#endif
+
+       ret = setsockopt(sockfd, TC_IPPROTO, SO_SET_ADD_COUNTERS,
+                        newcounters, counterlen);
+       if (ret < 0)
+               goto out_free_newcounters;
+
+       free(repl->counters);
+       free(repl);
+       free(newcounters);
+
+finished:
+       TC_FREE(handle);
+       return 1;
+
+out_free_newcounters:
+       free(newcounters);
+out_free_repl_counters:
+       free(repl->counters);
+out_free_repl:
+       free(repl);
+out_zero:
+       return 0;
+}
+
+/* Get raw socket. */
+int
+TC_GET_RAW_SOCKET(void)
+{
+       return sockfd;
+}
+
+/* Translates errno numbers into more human-readable form than strerror. */
+const char *
+TC_STRERROR(int err)
+{
+       unsigned int i;
+       struct table_struct {
+               void *fn;
+               int err;
+               const char *message;
+       } table [] =
+         { { TC_INIT, EPERM, "Permission denied (you must be root)" },
+           { TC_INIT, EINVAL, "Module is wrong version" },
+           { TC_INIT, ENOENT, 
+                   "Table does not exist (do you need to insmod?)" },
+           { TC_DELETE_CHAIN, ENOTEMPTY, "Chain is not empty" },
+           { TC_DELETE_CHAIN, EINVAL, "Can't delete built-in chain" },
+           { TC_DELETE_CHAIN, EMLINK,
+             "Can't delete chain with references left" },
+           { TC_CREATE_CHAIN, EEXIST, "Chain already exists" },
+           { TC_INSERT_ENTRY, E2BIG, "Index of insertion too big" },
+           { TC_REPLACE_ENTRY, E2BIG, "Index of replacement too big" },
+           { TC_DELETE_NUM_ENTRY, E2BIG, "Index of deletion too big" },
+           { TC_READ_COUNTER, E2BIG, "Index of counter too big" },
+           { TC_ZERO_COUNTER, E2BIG, "Index of counter too big" },
+           { TC_INSERT_ENTRY, ELOOP, "Loop found in table" },
+           { TC_INSERT_ENTRY, EINVAL, "Target problem" },
+           /* EINVAL for CHECK probably means bad interface. */
+           { TC_CHECK_PACKET, EINVAL,
+             "Bad arguments (does that interface exist?)" },
+           { TC_CHECK_PACKET, ENOSYS,
+             "Checking will most likely never get implemented" },
+           /* ENOENT for DELETE probably means no matching rule */
+           { TC_DELETE_ENTRY, ENOENT,
+             "Bad rule (does a matching rule exist in that chain?)" },
+           { TC_SET_POLICY, ENOENT,
+             "Bad built-in chain name" },
+           { TC_SET_POLICY, EINVAL,
+             "Bad policy name" },
+
+           { NULL, 0, "Incompatible with this kernel" },
+           { NULL, ENOPROTOOPT, "iptables who? (do you need to insmod?)" },
+           { NULL, ENOSYS, "Will be implemented real soon.  I promise ;)" },
+           { NULL, ENOMEM, "Memory allocation problem" },
+           { NULL, ENOENT, "No chain/target/match by that name" },
+         };
+
+       for (i = 0; i < sizeof(table)/sizeof(struct table_struct); i++) {
+               if ((!table[i].fn || table[i].fn == iptc_fn)
+                   && table[i].err == err)
+                       return table[i].message;
+       }
+
+       return strerror(err);
+}
diff --git a/src/libiptc/libiptc.h b/src/libiptc/libiptc.h
new file mode 100644 (file)
index 0000000..d9f7423
--- /dev/null
@@ -0,0 +1,185 @@
+/**
+ * This file was imported from the iptables sources.
+ * Copyright (C) 1999-2008 Netfilter Core Team
+ *
+ * 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; only version 2 of the License is applicable.
+ *
+ * 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 St, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+#ifndef _LIBIPTC_H
+#define _LIBIPTC_H
+/* Library which manipulates filtering rules. */
+
+#include <linux/types.h>
+#include "ipt_kernel_headers.h"
+#include <linux/netfilter_ipv4/ip_tables.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef IPT_MIN_ALIGN
+/* ipt_entry has pointers and u_int64_t's in it, so if you align to
+   it, you'll also align to any crazy matches and targets someone
+   might write */
+#define IPT_MIN_ALIGN (__alignof__(struct ipt_entry))
+#endif
+
+#define IPT_ALIGN(s) (((s) + ((IPT_MIN_ALIGN)-1)) & ~((IPT_MIN_ALIGN)-1))
+
+typedef char ipt_chainlabel[32];
+
+#define IPTC_LABEL_ACCEPT  "ACCEPT"
+#define IPTC_LABEL_DROP    "DROP"
+#define IPTC_LABEL_QUEUE   "QUEUE"
+#define IPTC_LABEL_RETURN  "RETURN"
+
+/* Transparent handle type. */
+typedef struct iptc_handle *iptc_handle_t;
+
+/* Does this chain exist? */
+int iptc_is_chain(const char *chain, const iptc_handle_t handle);
+
+/* Take a snapshot of the rules.  Returns NULL on error. */
+iptc_handle_t iptc_init(const char *tablename);
+
+/* Cleanup after iptc_init(). */
+void iptc_free(iptc_handle_t *h);
+
+/* Iterator functions to run through the chains.  Returns NULL at end. */
+const char *iptc_first_chain(iptc_handle_t *handle);
+const char *iptc_next_chain(iptc_handle_t *handle);
+
+/* Get first rule in the given chain: NULL for empty chain. */
+const struct ipt_entry *iptc_first_rule(const char *chain,
+                                       iptc_handle_t *handle);
+
+/* Returns NULL when rules run out. */
+const struct ipt_entry *iptc_next_rule(const struct ipt_entry *prev,
+                                      iptc_handle_t *handle);
+
+/* Returns a pointer to the target name of this entry. */
+const char *iptc_get_target(const struct ipt_entry *e,
+                           iptc_handle_t *handle);
+
+/* Is this a built-in chain? */
+int iptc_builtin(const char *chain, const iptc_handle_t handle);
+
+/* Get the policy of a given built-in chain */
+const char *iptc_get_policy(const char *chain,
+                           struct ipt_counters *counter,
+                           iptc_handle_t *handle);
+
+/* These functions return TRUE for OK or 0 and set errno.  If errno ==
+   0, it means there was a version error (ie. upgrade libiptc). */
+/* Rule numbers start at 1 for the first rule. */
+
+/* Insert the entry `e' in chain `chain' into position `rulenum'. */
+int iptc_insert_entry(const ipt_chainlabel chain,
+                     const struct ipt_entry *e,
+                     unsigned int rulenum,
+                     iptc_handle_t *handle);
+
+/* Atomically replace rule `rulenum' in `chain' with `e'. */
+int iptc_replace_entry(const ipt_chainlabel chain,
+                      const struct ipt_entry *e,
+                      unsigned int rulenum,
+                      iptc_handle_t *handle);
+
+/* Append entry `e' to chain `chain'.  Equivalent to insert with
+   rulenum = length of chain. */
+int iptc_append_entry(const ipt_chainlabel chain,
+                     const struct ipt_entry *e,
+                     iptc_handle_t *handle);
+
+/* Delete the first rule in `chain' which matches `e', subject to
+   matchmask (array of length == origfw) */
+int iptc_delete_entry(const ipt_chainlabel chain,
+                     const struct ipt_entry *origfw,
+                     unsigned char *matchmask,
+                     iptc_handle_t *handle);
+
+/* Delete the rule in position `rulenum' in `chain'. */
+int iptc_delete_num_entry(const ipt_chainlabel chain,
+                         unsigned int rulenum,
+                         iptc_handle_t *handle);
+
+/* Check the packet `e' on chain `chain'.  Returns the verdict, or
+   NULL and sets errno. */
+const char *iptc_check_packet(const ipt_chainlabel chain,
+                             struct ipt_entry *entry,
+                             iptc_handle_t *handle);
+
+/* Flushes the entries in the given chain (ie. empties chain). */
+int iptc_flush_entries(const ipt_chainlabel chain,
+                      iptc_handle_t *handle);
+
+/* Zeroes the counters in a chain. */
+int iptc_zero_entries(const ipt_chainlabel chain,
+                     iptc_handle_t *handle);
+
+/* Creates a new chain. */
+int iptc_create_chain(const ipt_chainlabel chain,
+                     iptc_handle_t *handle);
+
+/* Deletes a chain. */
+int iptc_delete_chain(const ipt_chainlabel chain,
+                     iptc_handle_t *handle);
+
+/* Renames a chain. */
+int iptc_rename_chain(const ipt_chainlabel oldname,
+                     const ipt_chainlabel newname,
+                     iptc_handle_t *handle);
+
+/* Sets the policy on a built-in chain. */
+int iptc_set_policy(const ipt_chainlabel chain,
+                   const ipt_chainlabel policy,
+                   struct ipt_counters *counters,
+                   iptc_handle_t *handle);
+
+/* Get the number of references to this chain */
+int iptc_get_references(unsigned int *ref,
+                       const ipt_chainlabel chain,
+                       iptc_handle_t *handle);
+
+/* read packet and byte counters for a specific rule */
+struct ipt_counters *iptc_read_counter(const ipt_chainlabel chain,
+                                      unsigned int rulenum,
+                                      iptc_handle_t *handle);
+
+/* zero packet and byte counters for a specific rule */
+int iptc_zero_counter(const ipt_chainlabel chain,
+                     unsigned int rulenum,
+                     iptc_handle_t *handle);
+
+/* set packet and byte counters for a specific rule */
+int iptc_set_counter(const ipt_chainlabel chain,
+                    unsigned int rulenum,
+                    struct ipt_counters *counters,
+                    iptc_handle_t *handle);
+
+/* Makes the actual changes. */
+int iptc_commit(iptc_handle_t *handle);
+
+/* Get raw socket. */
+int iptc_get_raw_socket();
+
+/* Translates errno numbers into more human-readable form than strerror. */
+const char *iptc_strerror(int err);
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif /* _LIBIPTC_H */
diff --git a/src/libiptc/linux_list.h b/src/libiptc/linux_list.h
new file mode 100644 (file)
index 0000000..56d9a26
--- /dev/null
@@ -0,0 +1,741 @@
+/**
+ * This file was imported from the iptables sources.
+ * Copyright (C) 1999-2008 Netfilter Core Team
+ *
+ * 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; only version 2 of the License is applicable.
+ *
+ * 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 St, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+#ifndef _LINUX_LIST_H
+#define _LINUX_LIST_H
+
+#undef offsetof
+#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
+
+/**
+ * container_of - cast a member of a structure out to the containing structure
+ *
+ * @ptr:       the pointer to the member.
+ * @type:      the type of the container struct this is embedded in.
+ * @member:    the name of the member within the struct.
+ *
+ */
+#define container_of(ptr, type, member) ({                     \
+        const typeof( ((type *)0)->member ) *__mptr = (ptr);   \
+        (type *)( (char *)__mptr - offsetof(type,member) );})
+
+/*
+ * Check at compile time that something is of a particular type.
+ * Always evaluates to 1 so you may use it easily in comparisons.
+ */
+#define typecheck(type,x) \
+({     type __dummy; \
+       typeof(x) __dummy2; \
+       (void)(&__dummy == &__dummy2); \
+       1; \
+})
+
+#define prefetch(x)            1
+
+/* empty define to make this work in userspace -HW */
+#define smp_wmb()
+
+/*
+ * These are non-NULL pointers that will result in page faults
+ * under normal circumstances, used to verify that nobody uses
+ * non-initialized list entries.
+ */
+#define LIST_POISON1  ((void *) 0x00100100)
+#define LIST_POISON2  ((void *) 0x00200200)
+
+/*
+ * Simple doubly linked list implementation.
+ *
+ * Some of the internal functions ("__xxx") are useful when
+ * manipulating whole lists rather than single entries, as
+ * sometimes we already know the next/prev entries and we can
+ * generate better code by using them directly rather than
+ * using the generic single-entry routines.
+ */
+
+struct list_head {
+       struct list_head *next, *prev;
+};
+
+#define LIST_HEAD_INIT(name) { &(name), &(name) }
+
+#define LIST_HEAD(name) \
+       struct list_head name = LIST_HEAD_INIT(name)
+
+#define INIT_LIST_HEAD(ptr) do { \
+       (ptr)->next = (ptr); (ptr)->prev = (ptr); \
+} while (0)
+
+/*
+ * Insert a new entry between two known consecutive entries.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __list_add(struct list_head *new,
+                             struct list_head *prev,
+                             struct list_head *next)
+{
+       next->prev = new;
+       new->next = next;
+       new->prev = prev;
+       prev->next = new;
+}
+
+/**
+ * list_add - add a new entry
+ * @new: new entry to be added
+ * @head: list head to add it after
+ *
+ * Insert a new entry after the specified head.
+ * This is good for implementing stacks.
+ */
+static inline void list_add(struct list_head *new, struct list_head *head)
+{
+       __list_add(new, head, head->next);
+}
+
+/**
+ * list_add_tail - add a new entry
+ * @new: new entry to be added
+ * @head: list head to add it before
+ *
+ * Insert a new entry before the specified head.
+ * This is useful for implementing queues.
+ */
+static inline void list_add_tail(struct list_head *new, struct list_head *head)
+{
+       __list_add(new, head->prev, head);
+}
+
+/*
+ * Insert a new entry between two known consecutive entries.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __list_add_rcu(struct list_head * new,
+               struct list_head * prev, struct list_head * next)
+{
+       new->next = next;
+       new->prev = prev;
+       smp_wmb();
+       next->prev = new;
+       prev->next = new;
+}
+
+/**
+ * list_add_rcu - add a new entry to rcu-protected list
+ * @new: new entry to be added
+ * @head: list head to add it after
+ *
+ * Insert a new entry after the specified head.
+ * This is good for implementing stacks.
+ *
+ * The caller must take whatever precautions are necessary
+ * (such as holding appropriate locks) to avoid racing
+ * with another list-mutation primitive, such as list_add_rcu()
+ * or list_del_rcu(), running on this same list.
+ * However, it is perfectly legal to run concurrently with
+ * the _rcu list-traversal primitives, such as
+ * list_for_each_entry_rcu().
+ */
+static inline void list_add_rcu(struct list_head *new, struct list_head *head)
+{
+       __list_add_rcu(new, head, head->next);
+}
+
+/**
+ * list_add_tail_rcu - add a new entry to rcu-protected list
+ * @new: new entry to be added
+ * @head: list head to add it before
+ *
+ * Insert a new entry before the specified head.
+ * This is useful for implementing queues.
+ *
+ * The caller must take whatever precautions are necessary
+ * (such as holding appropriate locks) to avoid racing
+ * with another list-mutation primitive, such as list_add_tail_rcu()
+ * or list_del_rcu(), running on this same list.
+ * However, it is perfectly legal to run concurrently with
+ * the _rcu list-traversal primitives, such as
+ * list_for_each_entry_rcu().
+ */
+static inline void list_add_tail_rcu(struct list_head *new,
+                                       struct list_head *head)
+{
+       __list_add_rcu(new, head->prev, head);
+}
+
+/*
+ * Delete a list entry by making the prev/next entries
+ * point to each other.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __list_del(struct list_head * prev, struct list_head * next)
+{
+       next->prev = prev;
+       prev->next = next;
+}
+
+/**
+ * list_del - deletes entry from list.
+ * @entry: the element to delete from the list.
+ * Note: list_empty on entry does not return true after this, the entry is
+ * in an undefined state.
+ */
+static inline void list_del(struct list_head *entry)
+{
+       __list_del(entry->prev, entry->next);
+       entry->next = LIST_POISON1;
+       entry->prev = LIST_POISON2;
+}
+
+/**
+ * list_del_rcu - deletes entry from list without re-initialization
+ * @entry: the element to delete from the list.
+ *
+ * Note: list_empty on entry does not return true after this,
+ * the entry is in an undefined state. It is useful for RCU based
+ * lockfree traversal.
+ *
+ * In particular, it means that we can not poison the forward
+ * pointers that may still be used for walking the list.
+ *
+ * The caller must take whatever precautions are necessary
+ * (such as holding appropriate locks) to avoid racing
+ * with another list-mutation primitive, such as list_del_rcu()
+ * or list_add_rcu(), running on this same list.
+ * However, it is perfectly legal to run concurrently with
+ * the _rcu list-traversal primitives, such as
+ * list_for_each_entry_rcu().
+ *
+ * Note that the caller is not permitted to immediately free
+ * the newly deleted entry.  Instead, either synchronize_kernel()
+ * or call_rcu() must be used to defer freeing until an RCU
+ * grace period has elapsed.
+ */
+static inline void list_del_rcu(struct list_head *entry)
+{
+       __list_del(entry->prev, entry->next);
+       entry->prev = LIST_POISON2;
+}
+
+/**
+ * list_del_init - deletes entry from list and reinitialize it.
+ * @entry: the element to delete from the list.
+ */
+static inline void list_del_init(struct list_head *entry)
+{
+       __list_del(entry->prev, entry->next);
+       INIT_LIST_HEAD(entry);
+}
+
+/**
+ * list_move - delete from one list and add as another's head
+ * @list: the entry to move
+ * @head: the head that will precede our entry
+ */
+static inline void list_move(struct list_head *list, struct list_head *head)
+{
+        __list_del(list->prev, list->next);
+        list_add(list, head);
+}
+
+/**
+ * list_move_tail - delete from one list and add as another's tail
+ * @list: the entry to move
+ * @head: the head that will follow our entry
+ */
+static inline void list_move_tail(struct list_head *list,
+                                 struct list_head *head)
+{
+        __list_del(list->prev, list->next);
+        list_add_tail(list, head);
+}
+
+/**
+ * list_empty - tests whether a list is empty
+ * @head: the list to test.
+ */
+static inline int list_empty(const struct list_head *head)
+{
+       return head->next == head;
+}
+
+/**
+ * list_empty_careful - tests whether a list is
+ * empty _and_ checks that no other CPU might be
+ * in the process of still modifying either member
+ *
+ * NOTE: using list_empty_careful() without synchronization
+ * can only be safe if the only activity that can happen
+ * to the list entry is list_del_init(). Eg. it cannot be used
+ * if another CPU could re-list_add() it.
+ *
+ * @head: the list to test.
+ */
+static inline int list_empty_careful(const struct list_head *head)
+{
+       struct list_head *next = head->next;
+       return (next == head) && (next == head->prev);
+}
+
+static inline void __list_splice(struct list_head *list,
+                                struct list_head *head)
+{
+       struct list_head *first = list->next;
+       struct list_head *last = list->prev;
+       struct list_head *at = head->next;
+
+       first->prev = head;
+       head->next = first;
+
+       last->next = at;
+       at->prev = last;
+}
+
+/**
+ * list_splice - join two lists
+ * @list: the new list to add.
+ * @head: the place to add it in the first list.
+ */
+static inline void list_splice(struct list_head *list, struct list_head *head)
+{
+       if (!list_empty(list))
+               __list_splice(list, head);
+}
+
+/**
+ * list_splice_init - join two lists and reinitialise the emptied list.
+ * @list: the new list to add.
+ * @head: the place to add it in the first list.
+ *
+ * The list at @list is reinitialised
+ */
+static inline void list_splice_init(struct list_head *list,
+                                   struct list_head *head)
+{
+       if (!list_empty(list)) {
+               __list_splice(list, head);
+               INIT_LIST_HEAD(list);
+       }
+}
+
+/**
+ * list_entry - get the struct for this entry
+ * @ptr:       the &struct list_head pointer.
+ * @type:      the type of the struct this is embedded in.
+ * @member:    the name of the list_struct within the struct.
+ */
+#define list_entry(ptr, type, member) \
+       container_of(ptr, type, member)
+
+/**
+ * list_for_each       -       iterate over a list
+ * @pos:       the &struct list_head to use as a loop counter.
+ * @head:      the head for your list.
+ */
+#define list_for_each(pos, head) \
+       for (pos = (head)->next, prefetch(pos->next); pos != (head); \
+               pos = pos->next, prefetch(pos->next))
+
+/**
+ * __list_for_each     -       iterate over a list
+ * @pos:       the &struct list_head to use as a loop counter.
+ * @head:      the head for your list.
+ *
+ * This variant differs from list_for_each() in that it's the
+ * simplest possible list iteration code, no prefetching is done.
+ * Use this for code that knows the list to be very short (empty
+ * or 1 entry) most of the time.
+ */
+#define __list_for_each(pos, head) \
+       for (pos = (head)->next; pos != (head); pos = pos->next)
+
+/**
+ * list_for_each_prev  -       iterate over a list backwards
+ * @pos:       the &struct list_head to use as a loop counter.
+ * @head:      the head for your list.
+ */
+#define list_for_each_prev(pos, head) \
+       for (pos = (head)->prev, prefetch(pos->prev); pos != (head); \
+               pos = pos->prev, prefetch(pos->prev))
+
+/**
+ * list_for_each_safe  -       iterate over a list safe against removal of list entry
+ * @pos:       the &struct list_head to use as a loop counter.
+ * @n:         another &struct list_head to use as temporary storage
+ * @head:      the head for your list.
+ */
+#define list_for_each_safe(pos, n, head) \
+       for (pos = (head)->next, n = pos->next; pos != (head); \
+               pos = n, n = pos->next)
+
+/**
+ * list_for_each_entry -       iterate over list of given type
+ * @pos:       the type * to use as a loop counter.
+ * @head:      the head for your list.
+ * @member:    the name of the list_struct within the struct.
+ */
+#define list_for_each_entry(pos, head, member)                         \
+       for (pos = list_entry((head)->next, typeof(*pos), member),      \
+                    prefetch(pos->member.next);                        \
+            &pos->member != (head);                                    \
+            pos = list_entry(pos->member.next, typeof(*pos), member),  \
+                    prefetch(pos->member.next))
+
+/**
+ * list_for_each_entry_reverse - iterate backwards over list of given type.
+ * @pos:       the type * to use as a loop counter.
+ * @head:      the head for your list.
+ * @member:    the name of the list_struct within the struct.
+ */
+#define list_for_each_entry_reverse(pos, head, member)                 \
+       for (pos = list_entry((head)->prev, typeof(*pos), member),      \
+                    prefetch(pos->member.prev);                        \
+            &pos->member != (head);                                    \
+            pos = list_entry(pos->member.prev, typeof(*pos), member),  \
+                    prefetch(pos->member.prev))
+
+/**
+ * list_prepare_entry - prepare a pos entry for use as a start point in
+ *                     list_for_each_entry_continue
+ * @pos:       the type * to use as a start point
+ * @head:      the head of the list
+ * @member:    the name of the list_struct within the struct.
+ */
+#define list_prepare_entry(pos, head, member) \
+       ((pos) ? : list_entry(head, typeof(*pos), member))
+
+/**
+ * list_for_each_entry_continue -      iterate over list of given type
+ *                     continuing after existing point
+ * @pos:       the type * to use as a loop counter.
+ * @head:      the head for your list.
+ * @member:    the name of the list_struct within the struct.
+ */
+#define list_for_each_entry_continue(pos, head, member)                \
+       for (pos = list_entry(pos->member.next, typeof(*pos), member),  \
+                    prefetch(pos->member.next);                        \
+            &pos->member != (head);                                    \
+            pos = list_entry(pos->member.next, typeof(*pos), member),  \
+                    prefetch(pos->member.next))
+
+/**
+ * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry
+ * @pos:       the type * to use as a loop counter.
+ * @n:         another type * to use as temporary storage
+ * @head:      the head for your list.
+ * @member:    the name of the list_struct within the struct.
+ */
+#define list_for_each_entry_safe(pos, n, head, member)                 \
+       for (pos = list_entry((head)->next, typeof(*pos), member),      \
+               n = list_entry(pos->member.next, typeof(*pos), member); \
+            &pos->member != (head);                                    \
+            pos = n, n = list_entry(n->member.next, typeof(*n), member))
+
+/**
+ * list_for_each_rcu   -       iterate over an rcu-protected list
+ * @pos:       the &struct list_head to use as a loop counter.
+ * @head:      the head for your list.
+ *
+ * This list-traversal primitive may safely run concurrently with
+ * the _rcu list-mutation primitives such as list_add_rcu()
+ * as long as the traversal is guarded by rcu_read_lock().
+ */
+#define list_for_each_rcu(pos, head) \
+       for (pos = (head)->next, prefetch(pos->next); pos != (head); \
+               pos = pos->next, ({ smp_read_barrier_depends(); 0;}), prefetch(pos->next))
+
+#define __list_for_each_rcu(pos, head) \
+       for (pos = (head)->next; pos != (head); \
+               pos = pos->next, ({ smp_read_barrier_depends(); 0;}))
+
+/**
+ * list_for_each_safe_rcu      -       iterate over an rcu-protected list safe
+ *                                     against removal of list entry
+ * @pos:       the &struct list_head to use as a loop counter.
+ * @n:         another &struct list_head to use as temporary storage
+ * @head:      the head for your list.
+ *
+ * This list-traversal primitive may safely run concurrently with
+ * the _rcu list-mutation primitives such as list_add_rcu()
+ * as long as the traversal is guarded by rcu_read_lock().
+ */
+#define list_for_each_safe_rcu(pos, n, head) \
+       for (pos = (head)->next, n = pos->next; pos != (head); \
+               pos = n, ({ smp_read_barrier_depends(); 0;}), n = pos->next)
+
+/**
+ * list_for_each_entry_rcu     -       iterate over rcu list of given type
+ * @pos:       the type * to use as a loop counter.
+ * @head:      the head for your list.
+ * @member:    the name of the list_struct within the struct.
+ *
+ * This list-traversal primitive may safely run concurrently with
+ * the _rcu list-mutation primitives such as list_add_rcu()
+ * as long as the traversal is guarded by rcu_read_lock().
+ */
+#define list_for_each_entry_rcu(pos, head, member)                     \
+       for (pos = list_entry((head)->next, typeof(*pos), member),      \
+                    prefetch(pos->member.next);                        \
+            &pos->member != (head);                                    \
+            pos = list_entry(pos->member.next, typeof(*pos), member),  \
+                    ({ smp_read_barrier_depends(); 0;}),               \
+                    prefetch(pos->member.next))
+
+
+/**
+ * list_for_each_continue_rcu  -       iterate over an rcu-protected list
+ *                     continuing after existing point.
+ * @pos:       the &struct list_head to use as a loop counter.
+ * @head:      the head for your list.
+ *
+ * This list-traversal primitive may safely run concurrently with
+ * the _rcu list-mutation primitives such as list_add_rcu()
+ * as long as the traversal is guarded by rcu_read_lock().
+ */
+#define list_for_each_continue_rcu(pos, head) \
+       for ((pos) = (pos)->next, prefetch((pos)->next); (pos) != (head); \
+               (pos) = (pos)->next, ({ smp_read_barrier_depends(); 0;}), prefetch((pos)->next))
+
+/*
+ * Double linked lists with a single pointer list head.
+ * Mostly useful for hash tables where the two pointer list head is
+ * too wasteful.
+ * You lose the ability to access the tail in O(1).
+ */
+
+struct hlist_head {
+       struct hlist_node *first;
+};
+
+struct hlist_node {
+       struct hlist_node *next, **pprev;
+};
+
+#define HLIST_HEAD_INIT { .first = NULL }
+#define HLIST_HEAD(name) struct hlist_head name = {  .first = NULL }
+#define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL)
+#define INIT_HLIST_NODE(ptr) ((ptr)->next = NULL, (ptr)->pprev = NULL)
+
+static inline int hlist_unhashed(const struct hlist_node *h)
+{
+       return !h->pprev;
+}
+
+static inline int hlist_empty(const struct hlist_head *h)
+{
+       return !h->first;
+}
+
+static inline void __hlist_del(struct hlist_node *n)
+{
+       struct hlist_node *next = n->next;
+       struct hlist_node **pprev = n->pprev;
+       *pprev = next;
+       if (next)
+               next->pprev = pprev;
+}
+
+static inline void hlist_del(struct hlist_node *n)
+{
+       __hlist_del(n);
+       n->next = LIST_POISON1;
+       n->pprev = LIST_POISON2;
+}
+
+/**
+ * hlist_del_rcu - deletes entry from hash list without re-initialization
+ * @n: the element to delete from the hash list.
+ *
+ * Note: list_unhashed() on entry does not return true after this,
+ * the entry is in an undefined state. It is useful for RCU based
+ * lockfree traversal.
+ *
+ * In particular, it means that we can not poison the forward
+ * pointers that may still be used for walking the hash list.
+ *
+ * The caller must take whatever precautions are necessary
+ * (such as holding appropriate locks) to avoid racing
+ * with another list-mutation primitive, such as hlist_add_head_rcu()
+ * or hlist_del_rcu(), running on this same list.
+ * However, it is perfectly legal to run concurrently with
+ * the _rcu list-traversal primitives, such as
+ * hlist_for_each_entry().
+ */
+static inline void hlist_del_rcu(struct hlist_node *n)
+{
+       __hlist_del(n);
+       n->pprev = LIST_POISON2;
+}
+
+static inline void hlist_del_init(struct hlist_node *n)
+{
+       if (n->pprev)  {
+               __hlist_del(n);
+               INIT_HLIST_NODE(n);
+       }
+}
+
+#define hlist_del_rcu_init hlist_del_init
+
+static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h)
+{
+       struct hlist_node *first = h->first;
+       n->next = first;
+       if (first)
+               first->pprev = &n->next;
+       h->first = n;
+       n->pprev = &h->first;
+}
+
+
+/**
+ * hlist_add_head_rcu - adds the specified element to the specified hlist,
+ * while permitting racing traversals.
+ * @n: the element to add to the hash list.
+ * @h: the list to add to.
+ *
+ * The caller must take whatever precautions are necessary
+ * (such as holding appropriate locks) to avoid racing
+ * with another list-mutation primitive, such as hlist_add_head_rcu()
+ * or hlist_del_rcu(), running on this same list.
+ * However, it is perfectly legal to run concurrently with
+ * the _rcu list-traversal primitives, such as
+ * hlist_for_each_entry(), but only if smp_read_barrier_depends()
+ * is used to prevent memory-consistency problems on Alpha CPUs.
+ * Regardless of the type of CPU, the list-traversal primitive
+ * must be guarded by rcu_read_lock().
+ *
+ * OK, so why don't we have an hlist_for_each_entry_rcu()???
+ */
+static inline void hlist_add_head_rcu(struct hlist_node *n,
+                                       struct hlist_head *h)
+{
+       struct hlist_node *first = h->first;
+       n->next = first;
+       n->pprev = &h->first;
+       smp_wmb();
+       if (first)
+               first->pprev = &n->next;
+       h->first = n;
+}
+
+/* next must be != NULL */
+static inline void hlist_add_before(struct hlist_node *n,
+                                       struct hlist_node *next)
+{
+       n->pprev = next->pprev;
+       n->next = next;
+       next->pprev = &n->next;
+       *(n->pprev) = n;
+}
+
+static inline void hlist_add_after(struct hlist_node *n,
+                                       struct hlist_node *next)
+{
+       next->next = n->next;
+       n->next = next;
+       next->pprev = &n->next;
+
+       if(next->next)
+               next->next->pprev  = &next->next;
+}
+
+#define hlist_entry(ptr, type, member) container_of(ptr,type,member)
+
+#define hlist_for_each(pos, head) \
+       for (pos = (head)->first; pos && ({ prefetch(pos->next); 1; }); \
+            pos = pos->next)
+
+#define hlist_for_each_safe(pos, n, head) \
+       for (pos = (head)->first; pos && ({ n = pos->next; 1; }); \
+            pos = n)
+
+/**
+ * hlist_for_each_entry        - iterate over list of given type
+ * @tpos:      the type * to use as a loop counter.
+ * @pos:       the &struct hlist_node to use as a loop counter.
+ * @head:      the head for your list.
+ * @member:    the name of the hlist_node within the struct.
+ */
+#define hlist_for_each_entry(tpos, pos, head, member)                   \
+       for (pos = (head)->first;                                        \
+            pos && ({ prefetch(pos->next); 1;}) &&                      \
+               ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
+            pos = pos->next)
+
+/**
+ * hlist_for_each_entry_continue - iterate over a hlist continuing after existing point
+ * @tpos:      the type * to use as a loop counter.
+ * @pos:       the &struct hlist_node to use as a loop counter.
+ * @member:    the name of the hlist_node within the struct.
+ */
+#define hlist_for_each_entry_continue(tpos, pos, member)                \
+       for (pos = (pos)->next;                                          \
+            pos && ({ prefetch(pos->next); 1;}) &&                      \
+               ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
+            pos = pos->next)
+
+/**
+ * hlist_for_each_entry_from - iterate over a hlist continuing from existing point
+ * @tpos:      the type * to use as a loop counter.
+ * @pos:       the &struct hlist_node to use as a loop counter.
+ * @member:    the name of the hlist_node within the struct.
+ */
+#define hlist_for_each_entry_from(tpos, pos, member)                    \
+       for (; pos && ({ prefetch(pos->next); 1;}) &&                    \
+               ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
+            pos = pos->next)
+
+/**
+ * hlist_for_each_entry_safe - iterate over list of given type safe against removal of list entry
+ * @tpos:      the type * to use as a loop counter.
+ * @pos:       the &struct hlist_node to use as a loop counter.
+ * @n:         another &struct hlist_node to use as temporary storage
+ * @head:      the head for your list.
+ * @member:    the name of the hlist_node within the struct.
+ */
+#define hlist_for_each_entry_safe(tpos, pos, n, head, member)           \
+       for (pos = (head)->first;                                        \
+            pos && ({ n = pos->next; 1; }) &&                           \
+               ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
+            pos = n)
+
+/**
+ * hlist_for_each_entry_rcu - iterate over rcu list of given type
+ * @pos:       the type * to use as a loop counter.
+ * @pos:       the &struct hlist_node to use as a loop counter.
+ * @head:      the head for your list.
+ * @member:    the name of the hlist_node within the struct.
+ *
+ * This list-traversal primitive may safely run concurrently with
+ * the _rcu list-mutation primitives such as hlist_add_rcu()
+ * as long as the traversal is guarded by rcu_read_lock().
+ */
+#define hlist_for_each_entry_rcu(tpos, pos, head, member)               \
+       for (pos = (head)->first;                                        \
+            pos && ({ prefetch(pos->next); 1;}) &&                      \
+               ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
+            pos = pos->next, ({ smp_read_barrier_depends(); 0; }) )
+
+#endif
index 022e46a..0f558ca 100644 (file)
@@ -1767,9 +1767,25 @@ static int network_init (void)
        return (0);
 } /* int network_init */
 
+static int network_flush (int timeout)
+{
+       pthread_mutex_lock (&send_buffer_lock);
+
+       if (((time (NULL) - cache_flush_last) >= timeout)
+                       && (send_buffer_fill > 0))
+       {
+               flush_buffer ();
+       }
+
+       pthread_mutex_unlock (&send_buffer_lock);
+
+       return (0);
+} /* int network_flush */
+
 void module_register (void)
 {
        plugin_register_config ("network", network_config,
                        config_keys, config_keys_num);
        plugin_register_init   ("network", network_init);
+       plugin_register_flush   ("network", network_flush);
 } /* void module_register */
index a5201a0..283e8f6 100644 (file)
 
 #include <curl/curl.h>
 
-static char *url    = NULL;
-static char *user   = NULL;
-static char *pass   = NULL;
-static char *cacert = NULL;
+static char *url         = NULL;
+static char *user        = NULL;
+static char *pass        = NULL;
+static char *verify_peer = NULL;
+static char *verify_host = NULL;
+static char *cacert      = NULL;
 
 static CURL *curl = NULL;
 
@@ -44,6 +46,8 @@ static const char *config_keys[] =
   "URL",
   "User",
   "Password",
+  "VerifyPeer",
+  "VerifyHost",
   "CACert"
 };
 static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
@@ -89,6 +93,10 @@ static int config (const char *key, const char *value)
     return (config_set (&user, value));
   else if (strcasecmp (key, "password") == 0)
     return (config_set (&pass, value));
+  else if (strcasecmp (key, "verifypeer") == 0)
+    return (config_set (&verify_peer, value));
+  else if (strcasecmp (key, "verifyhost") == 0)
+    return (config_set (&verify_host, value));
   else if (strcasecmp (key, "cacert") == 0)
     return (config_set (&cacert, value));
   else
@@ -128,6 +136,24 @@ static int init (void)
     curl_easy_setopt (curl, CURLOPT_URL, url);
   }
 
+  if ((verify_peer == NULL) || (strcmp (verify_peer, "true") == 0))
+  {
+    curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, 1);
+  }
+  else
+  {
+    curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, 0);
+  }
+
+  if ((verify_host == NULL) || (strcmp (verify_host, "true") == 0))
+  {
+    curl_easy_setopt (curl, CURLOPT_SSL_VERIFYHOST, 2);
+  }
+  else
+  {
+    curl_easy_setopt (curl, CURLOPT_SSL_VERIFYHOST, 0);
+  }
+
   if (cacert != NULL)
   {
     curl_easy_setopt (curl, CURLOPT_CAINFO, cacert);
index 8061e0b..84dd537 100644 (file)
@@ -67,8 +67,9 @@
 #define PLUGIN_SHUTDOWN 3
 #define PLUGIN_LOG      4
 #define PLUGIN_NOTIF    5
+#define PLUGIN_FLUSH    6
 
-#define PLUGIN_TYPES    6
+#define PLUGIN_TYPES    7
 
 #define PLUGIN_DATASET  255
 
@@ -83,6 +84,8 @@ void boot_DynaLoader (PerlInterpreter *, CV *);
 static XS (Collectd_plugin_register_ds);
 static XS (Collectd_plugin_unregister_ds);
 static XS (Collectd_plugin_dispatch_values);
+static XS (Collectd_plugin_flush_one);
+static XS (Collectd_plugin_flush_all);
 static XS (Collectd_plugin_dispatch_notification);
 static XS (Collectd_plugin_log);
 static XS (Collectd_call_by_name);
@@ -136,6 +139,8 @@ static struct {
        { "Collectd::plugin_register_data_set",   Collectd_plugin_register_ds },
        { "Collectd::plugin_unregister_data_set", Collectd_plugin_unregister_ds },
        { "Collectd::plugin_dispatch_values",     Collectd_plugin_dispatch_values },
+       { "Collectd::plugin_flush_one",           Collectd_plugin_flush_one },
+       { "Collectd::plugin_flush_all",           Collectd_plugin_flush_all },
        { "Collectd::plugin_dispatch_notification",
                Collectd_plugin_dispatch_notification },
        { "Collectd::plugin_log",                 Collectd_plugin_log },
@@ -154,6 +159,7 @@ struct {
        { "Collectd::TYPE_SHUTDOWN",   PLUGIN_SHUTDOWN },
        { "Collectd::TYPE_LOG",        PLUGIN_LOG },
        { "Collectd::TYPE_NOTIF",      PLUGIN_NOTIF },
+       { "Collectd::TYPE_FLUSH",      PLUGIN_FLUSH },
        { "Collectd::TYPE_DATASET",    PLUGIN_DATASET },
        { "Collectd::DS_TYPE_COUNTER", DS_TYPE_COUNTER },
        { "Collectd::DS_TYPE_GAUGE",   DS_TYPE_GAUGE },
@@ -761,6 +767,12 @@ static int pplugin_call_all (pTHX_ int type, ...)
 
                XPUSHs (sv_2mortal (newRV_noinc ((SV *)notif)));
        }
+       else if (PLUGIN_FLUSH == type) {
+               /*
+                * $_[0] = $timeout;
+                */
+               XPUSHs (sv_2mortal (newSViv (va_arg (ap, int))));
+       }
 
        PUTBACK;
 
@@ -897,6 +909,54 @@ static XS (Collectd_plugin_dispatch_values)
 } /* static XS (Collectd_plugin_dispatch_values) */
 
 /*
+ * Collectd::plugin_flush_one (timeout, name).
+ *
+ * timeout:
+ *   timeout to use when flushing the data
+ *
+ * name:
+ *   name of the plugin to flush
+ */
+static XS (Collectd_plugin_flush_one)
+{
+       dXSARGS;
+
+       if (2 != items) {
+               log_err ("Usage: Collectd::plugin_flush_one(timeout, name)");
+               XSRETURN_EMPTY;
+       }
+
+       log_debug ("Collectd::plugin_flush_one: timeout = %i, name = \"%s\"",
+                       (int)SvIV (ST (0)), SvPV_nolen (ST (1)));
+
+       if (0 == plugin_flush_one ((int)SvIV (ST (0)), SvPV_nolen (ST (1))))
+               XSRETURN_YES;
+       else
+               XSRETURN_EMPTY;
+} /* static XS (Collectd_plugin_flush_one) */
+
+/*
+ * Collectd::plugin_flush_all (timeout).
+ *
+ * timeout:
+ *   timeout to use when flushing the data
+ */
+static XS (Collectd_plugin_flush_all)
+{
+       dXSARGS;
+
+       if (1 != items) {
+               log_err ("Usage: Collectd::plugin_flush_all(timeout)");
+               XSRETURN_EMPTY;
+       }
+
+       log_debug ("Collectd::plugin_flush_all: timeout = %i", (int)SvIV (ST (0)));
+
+       plugin_flush_all ((int)SvIV (ST (0)));
+       XSRETURN_YES;
+} /* static XS (Collectd_plugin_flush_all) */
+
+/*
  * Collectd::plugin_dispatch_notification (notif).
  *
  * notif:
@@ -1064,7 +1124,7 @@ static c_ithread_t *c_ithread_create (PerlInterpreter *base)
 
        aTHX = t->interp;
 
-       if (NULL != base) {
+       if ((NULL != base) && (NULL != PL_endav)) {
                av_clear (PL_endav);
                av_undef (PL_endav);
                PL_endav = Nullav;
@@ -1201,6 +1261,25 @@ static int perl_notify (const notification_t *notif)
        return pplugin_call_all (aTHX_ PLUGIN_NOTIF, notif);
 } /* static int perl_notify (const notification_t *) */
 
+static int perl_flush (const int timeout)
+{
+       dTHX;
+
+       if (NULL == perl_threads)
+               return 0;
+
+       if (NULL == aTHX) {
+               c_ithread_t *t = NULL;
+
+               pthread_mutex_lock (&perl_threads->mutex);
+               t = c_ithread_create (perl_threads->head->interp);
+               pthread_mutex_unlock (&perl_threads->mutex);
+
+               aTHX = t->interp;
+       }
+       return pplugin_call_all (aTHX_ PLUGIN_FLUSH, timeout);
+} /* static int perl_flush (const int) */
+
 static int perl_shutdown (void)
 {
        c_ithread_t *t = NULL;
@@ -1232,6 +1311,7 @@ static int perl_shutdown (void)
        plugin_unregister_init ("perl");
        plugin_unregister_read ("perl");
        plugin_unregister_write ("perl");
+       plugin_unregister_flush ("perl");
 
        ret = pplugin_call_all (aTHX_ PLUGIN_SHUTDOWN);
 
@@ -1367,7 +1447,7 @@ static int init_pi (int argc, char **argv)
                log_err ("init_pi: pthread_key_create failed");
 
                /* this must not happen - cowardly giving up if it does */
-               exit (1);
+               return -1;
        }
 
 #ifdef __FreeBSD__
@@ -1402,7 +1482,13 @@ static int init_pi (int argc, char **argv)
 
        if (0 != perl_parse (aTHX_ xs_init, argc, argv, NULL)) {
                log_err ("init_pi: Unable to bootstrap Collectd.");
-               exit (1);
+
+               perl_destruct (perl_threads->head->interp);
+               perl_free (perl_threads->head->interp);
+               sfree (perl_threads);
+
+               pthread_key_delete (perl_thr_key);
+               return -1;
        }
 
        /* Set $0 to "collectd" because perl_parse() has to set it to "-e". */
@@ -1417,6 +1503,7 @@ static int init_pi (int argc, char **argv)
        plugin_register_read ("perl", perl_read);
 
        plugin_register_write ("perl", perl_write);
+       plugin_register_flush ("perl", perl_flush);
        plugin_register_shutdown ("perl", perl_shutdown);
        return 0;
 } /* static int init_pi (const char **, const int) */
@@ -1443,7 +1530,9 @@ static int perl_config_loadplugin (pTHX_ oconfig_item_t *ci)
                return (1);
        }
 
-       init_pi (perl_argc, perl_argv);
+       if (0 != init_pi (perl_argc, perl_argv))
+               return -1;
+
        assert (NULL != perl_threads);
        assert (NULL != perl_threads->head);
 
index 1715cac..fdc0425 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * collectd - src/plugin.c
- * Copyright (C) 2005,2006  Florian octo Forster
+ * Copyright (C) 2005-2008  Florian octo Forster
  *
  * 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
@@ -17,6 +17,7 @@
  *
  * Authors:
  *   Florian octo Forster <octo at verplant.org>
+ *   Sebastian Harl <sh at tokkee.org>
  **/
 
 #include "collectd.h"
@@ -53,6 +54,7 @@ typedef struct read_func_s read_func_t;
 static llist_t *list_init;
 static llist_t *list_read;
 static llist_t *list_write;
+static llist_t *list_flush;
 static llist_t *list_shutdown;
 static llist_t *list_log;
 static llist_t *list_notification;
@@ -437,6 +439,11 @@ int plugin_register_write (const char *name,
        return (register_callback (&list_write, name, (void *) callback));
 } /* int plugin_register_write */
 
+int plugin_register_flush (const char *name, int (*callback) (const int))
+{
+       return (register_callback (&list_flush, name, (void *) callback));
+} /* int plugin_register_flush */
+
 int plugin_register_shutdown (char *name,
                int (*callback) (void))
 {
@@ -531,6 +538,11 @@ int plugin_unregister_write (const char *name)
        return (plugin_unregister (list_write, name));
 }
 
+int plugin_unregister_flush (const char *name)
+{
+       return (plugin_unregister (list_flush, name));
+}
+
 int plugin_unregister_shutdown (const char *name)
 {
        return (plugin_unregister (list_shutdown, name));
@@ -649,6 +661,43 @@ void plugin_read_all (void)
        pthread_mutex_unlock (&read_lock);
 } /* void plugin_read_all */
 
+int plugin_flush_one (int timeout, const char *name)
+{
+       int (*callback) (int);
+       llentry_t *le;
+       int status;
+
+       if (list_flush == NULL)
+               return (-1);
+
+       le = llist_search (list_flush, name);
+       if (le == NULL)
+               return (-1);
+       callback = (int (*) (int)) le->value;
+
+       status = (*callback) (timeout);
+
+       return (status);
+} /* int plugin_flush_ont */
+
+void plugin_flush_all (int timeout)
+{
+       int (*callback) (int);
+       llentry_t *le;
+
+       if (list_flush == NULL)
+               return;
+
+       le = llist_head (list_flush);
+       while (le != NULL)
+       {
+               callback = (int (*) (int)) le->value;
+               le = le->next;
+
+               (*callback) (timeout);
+       }
+} /* void plugin_flush_all */
+
 void plugin_shutdown_all (void)
 {
        int (*callback) (void);
index 91978bd..318b547 100644 (file)
@@ -2,7 +2,7 @@
 #define PLUGIN_H
 /**
  * collectd - src/plugin.h
- * Copyright (C) 2005-2007  Florian octo Forster
+ * Copyright (C) 2005-2008  Florian octo Forster
  *
  * 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
@@ -19,6 +19,7 @@
  *
  * Authors:
  *   Florian octo Forster <octo at verplant.org>
+ *   Sebastian Harl <sh at tokkee.org>
  **/
 
 #include "collectd.h"
@@ -149,8 +150,11 @@ int plugin_load (const char *name);
 
 void plugin_init_all (void);
 void plugin_read_all (void);
+void plugin_flush_all (int timeout);
 void plugin_shutdown_all (void);
 
+int plugin_flush_one (int timeout, const char *name);
+
 /*
  * The `plugin_register_*' functions are used to make `config', `init',
  * `read', `write' and `shutdown' functions known to the plugin
@@ -167,6 +171,8 @@ int plugin_register_read (const char *name,
                int (*callback) (void));
 int plugin_register_write (const char *name,
                int (*callback) (const data_set_t *ds, const value_list_t *vl));
+int plugin_register_flush (const char *name,
+               int (*callback) (const int));
 int plugin_register_shutdown (char *name,
                int (*callback) (void));
 int plugin_register_data_set (const data_set_t *ds);
@@ -180,6 +186,7 @@ int plugin_unregister_complex_config (const char *name);
 int plugin_unregister_init (const char *name);
 int plugin_unregister_read (const char *name);
 int plugin_unregister_write (const char *name);
+int plugin_unregister_flush (const char *name);
 int plugin_unregister_shutdown (const char *name);
 int plugin_unregister_data_set (const char *name);
 int plugin_unregister_log (const char *name);
diff --git a/src/powerdns.c b/src/powerdns.c
new file mode 100644 (file)
index 0000000..644dd56
--- /dev/null
@@ -0,0 +1,972 @@
+/**
+ * collectd - src/powerdns.c
+ * Copyright (C) 2007-2008  C-Ware, Inc.
+ * Copyright (C) 2008       Florian Forster
+ *
+ * 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; only version 2 of the License is applicable.
+ *
+ * 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 St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Author:
+ *   Luke Heberling <lukeh at c-ware.com>
+ *   Florian Forster <octo at verplant.org>
+ *
+ * DESCRIPTION
+ *   Queries a PowerDNS control socket for statistics
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+#include "utils_llist.h"
+
+#include <sys/stat.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#ifndef UNIX_PATH_MAX
+# define UNIX_PATH_MAX sizeof (((struct sockaddr_un *)0)->sun_path)
+#endif
+#define FUNC_ERROR(func) do { char errbuf[1024]; ERROR ("powerdns plugin: %s failed: %s", func, sstrerror (errno, errbuf, sizeof (errbuf))); } while (0)
+
+#define SERVER_SOCKET  "/var/run/pdns.controlsocket"
+#define SERVER_COMMAND "SHOW *"
+
+#define RECURSOR_SOCKET  "/var/run/pdns_recursor.controlsocket"
+#define RECURSOR_COMMAND "get noerror-answers nxdomain-answers " \
+  "servfail-answers sys-msec user-msec qa-latency cache-entries cache-hits " \
+  "cache-misses questions"
+
+struct list_item_s;
+typedef struct list_item_s list_item_t;
+
+struct list_item_s
+{
+  enum
+  {
+    SRV_AUTHORITATIVE,
+    SRV_RECURSOR
+  } server_type;
+  int (*func) (list_item_t *item);
+  char *instance;
+
+  char **fields;
+  int fields_num;
+  char *command;
+
+  struct sockaddr_un sockaddr;
+  int socktype;
+};
+
+struct statname_lookup_s
+{
+  char *name;
+  char *type;
+  char *type_instance;
+};
+typedef struct statname_lookup_s statname_lookup_t;
+
+/* Description of statistics returned by the recursor: {{{
+all-outqueries      counts the number of outgoing UDP queries since starting
+answers0-1          counts the number of queries answered within 1 milisecond
+answers100-1000     counts the number of queries answered within 1 second
+answers10-100       counts the number of queries answered within 100 miliseconds
+answers1-10         counts the number of queries answered within 10 miliseconds
+answers-slow        counts the number of queries answered after 1 second
+cache-entries       shows the number of entries in the cache
+cache-hits          counts the number of cache hits since starting
+cache-misses        counts the number of cache misses since starting
+chain-resends       number of queries chained to existing outstanding query
+client-parse-errors counts number of client packets that could not be parsed
+concurrent-queries  shows the number of MThreads currently running
+dlg-only-drops      number of records dropped because of delegation only setting
+negcache-entries    shows the number of entries in the Negative answer cache
+noerror-answers     counts the number of times it answered NOERROR since starting
+nsspeeds-entries    shows the number of entries in the NS speeds map
+nsset-invalidations number of times an nsset was dropped because it no longer worked
+nxdomain-answers    counts the number of times it answered NXDOMAIN since starting
+outgoing-timeouts   counts the number of timeouts on outgoing UDP queries since starting
+qa-latency          shows the current latency average
+questions           counts all End-user initiated queries with the RD bit set
+resource-limits     counts number of queries that could not be performed because of resource limits
+server-parse-errors counts number of server replied packets that could not be parsed
+servfail-answers    counts the number of times it answered SERVFAIL since starting
+spoof-prevents      number of times PowerDNS considered itself spoofed, and dropped the data
+sys-msec            number of CPU milliseconds spent in 'system' mode
+tcp-client-overflow number of times an IP address was denied TCP access because it already had too many connections
+tcp-outqueries      counts the number of outgoing TCP queries since starting
+tcp-questions       counts all incoming TCP queries (since starting)
+throttled-out       counts the number of throttled outgoing UDP queries since starting
+throttle-entries    shows the number of entries in the throttle map
+unauthorized-tcp    number of TCP questions denied because of allow-from restrictions
+unauthorized-udp    number of UDP questions denied because of allow-from restrictions
+unexpected-packets  number of answers from remote servers that were unexpected (might point to spoofing)
+uptime              number of seconds process has been running (since 3.1.5)
+user-msec           number of CPU milliseconds spent in 'user' mode
+}}} */
+
+const char* const default_server_fields[] = /* {{{ */
+{
+  "latency"
+  "packetcache-hit",
+  "packetcache-miss",
+  "packetcache-size",
+  "query-cache-hit",
+  "query-cache-miss",
+  "recursing-answers",
+  "recursing-questions",
+  "tcp-answers",
+  "tcp-queries",
+  "udp-answers",
+  "udp-queries",
+}; /* }}} */
+int default_server_fields_num = STATIC_ARRAY_SIZE (default_server_fields);
+
+statname_lookup_t lookup_table[] = /* {{{ */
+{
+  /*********************
+   * Server statistics *
+   *********************/
+  /* Questions */
+  {"recursing-questions",    "dns_question", "recurse"},
+  {"tcp-queries",            "dns_question", "tcp"},
+  {"udp-queries",            "dns_question", "udp"},
+
+  /* Answers */
+  {"recursing-answers",      "dns_answer",   "recurse"},
+  {"tcp-answers",            "dns_answer",   "tcp"},
+  {"udp-answers",            "dns_answer",   "udp"},
+
+  /* Cache stuff */
+  {"packetcache-hit",        "cache_result", "packet-hit"},
+  {"packetcache-miss",       "cache_result", "packet-miss"},
+  {"packetcache-size",       "cache_size",   "packet"},
+  {"query-cache-hit",        "cache_result", "query-hit"},
+  {"query-cache-miss",       "cache_result", "query-miss"},
+
+  /* Latency */
+  {"latency",                "latency",      NULL},
+
+  /* Other stuff.. */
+  {"corrupt-packets",        "io_packets",   "corrupt"},
+  {"deferred-cache-inserts", "counter",      "cache-deferred_insert"},
+  {"deferred-cache-lookup",  "counter",      "cache-deferred_lookup"},
+  {"qsize-a",                "cache_size",   "answers"},
+  {"qsize-q",                "cache_size",   "questions"},
+  {"servfail-packets",       "io_packets",   "servfail"},
+  {"timedout-packets",       "io_packets",   "timeout"},
+  {"udp4-answers",           "dns_answer",   "udp4"},
+  {"udp4-queries",           "dns_question", "queries-udp4"},
+  {"udp6-answers",           "dns_answer",   "udp6"},
+  {"udp6-queries",           "dns_question", "queries-udp6"},
+
+  /***********************
+   * Recursor statistics *
+   ***********************/
+  /* Answers by return code */
+  {"noerror-answers",     "dns_rcode",    "NOERROR"},
+  {"nxdomain-answers",    "dns_rcode",    "NXDOMAIN"},
+  {"servfail-answers",    "dns_rcode",    "SERVFAIL"},
+
+  /* CPU utilization */
+  {"sys-msec",            "cpu",          "system"},
+  {"user-msec",           "cpu",          "user"},
+
+  /* Question-to-answer latency */
+  {"qa-latency",          "latency",      NULL},
+
+  /* Cache */
+  {"cache-entries",       "cache_size",   NULL},
+  {"cache-hits",          "cache_result", "hit"},
+  {"cache-misses",        "cache_result", "miss"},
+
+  /* Total number of questions.. */
+  {"questions",           "dns_qtype",    "total"},
+
+  /* All the other stuff.. */
+  {"all-outqueries",      "dns_question", "outgoing"},
+  {"answers0-1",          "dns_answer",   "0_1"},
+  {"answers1-10",         "dns_answer",   "1_10"},
+  {"answers10-100",       "dns_answer",   "10_100"},
+  {"answers100-1000",     "dns_answer",   "100_1000"},
+  {"answers-slow",        "dns_answer",   "slow"},
+  {"chain-resends",       "dns_question", "chained"},
+  {"client-parse-errors", "counter",      "drops-client_parse_error"},
+  {"concurrent-queries",  "dns_question", "concurrent"},
+  {"dlg-only-drops",      "counter",      "drops-delegation_only"},
+  {"negcache-entries",    "cache_size",   "negative"},
+  {"nsspeeds-entries",    "gauge",        "entries-ns_speeds"},
+  {"nsset-invalidations", "counter",      "ns_set_invalidation"},
+  {"outgoing-timeouts",   "counter",      "drops-timeout_outgoing"},
+  {"resource-limits",     "counter",      "drops-resource_limit"},
+  {"server-parse-errors", "counter",      "drops-server_parse_error"},
+  {"spoof-prevents",      "counter",      "drops-spoofed"},
+  {"tcp-client-overflow", "counter",      "denied-client_overflow_tcp"},
+  {"tcp-outqueries",      "dns_question", "outgoing-tcp"},
+  {"tcp-questions",       "dns_question", "incoming-tcp"},
+  {"throttled-out",       "dns_question", "outgoing-throttled"},
+  {"throttle-entries",    "gauge",        "entries-throttle"},
+  {"unauthorized-tcp",    "counter",      "denied-unauthorized_tcp"},
+  {"unauthorized-udp",    "counter",      "denied-unauthorized_udp"},
+  {"unexpected-packets",  "dns_answer",   "unexpected"}
+  /* {"uptime", "", ""} */
+}; /* }}} */
+int lookup_table_length = STATIC_ARRAY_SIZE (lookup_table);
+
+static llist_t *list = NULL;
+
+#define PDNS_LOCAL_SOCKPATH LOCALSTATEDIR"/run/"PACKAGE_NAME"-powerdns"
+static char *local_sockpath = NULL;
+
+/* TODO: Do this before 4.4:
+ * - Recursor:
+ *   - Complete list of known pdns -> collectd mappings.
+ * - Update the collectd.conf(5) manpage.
+ *
+ * -octo
+ */
+
+/* <http://doc.powerdns.com/recursor-stats.html> */
+static void submit (const char *plugin_instance, /* {{{ */
+    const char *pdns_type, const char *value)
+{
+  value_list_t vl = VALUE_LIST_INIT;
+  value_t values[1];
+
+  const char *type = NULL;
+  const char *type_instance = NULL;
+  const data_set_t *ds;
+
+  int i;
+
+  for (i = 0; i < lookup_table_length; i++)
+    if (strcmp (lookup_table[i].name, pdns_type) == 0)
+      break;
+
+  if (lookup_table[i].type == NULL)
+    return;
+
+  if (i >= lookup_table_length)
+  {
+    INFO ("powerdns plugin: submit: Not found in lookup table: %s = %s;",
+        pdns_type, value);
+    return;
+  }
+
+  type = lookup_table[i].type;
+  type_instance = lookup_table[i].type_instance;
+
+  ds = plugin_get_ds (type);
+  if (ds == NULL)
+  {
+    ERROR ("powerdns plugin: The lookup table returned type `%s', "
+        "but I cannot find it via `plugin_get_ds'.",
+        type);
+    return;
+  }
+
+  if (ds->ds_num != 1)
+  {
+    ERROR ("powerdns plugin: type `%s' has %i data sources, "
+        "but I can only handle one.",
+        type, ds->ds_num);
+    return;
+  }
+
+  if (ds->ds[0].type == DS_TYPE_GAUGE)
+  {
+    char *endptr = NULL;
+
+    values[0].gauge = strtod (value, &endptr);
+
+    if (endptr == value)
+    {
+      ERROR ("powerdns plugin: Cannot convert `%s' "
+          "to a floating point number.", value);
+      return;
+    }
+  }
+  else
+  {
+    char *endptr = NULL;
+
+    values[0].counter = strtoll (value, &endptr, 0);
+    if (endptr == value)
+    {
+      ERROR ("powerdns plugin: Cannot convert `%s' "
+          "to an integer number.", value);
+      return;
+    }
+  }
+
+  vl.values = values;
+  vl.values_len = 1;
+  vl.time = time (NULL);
+  sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+  sstrncpy (vl.plugin, "powerdns", sizeof (vl.plugin));
+  if (type_instance != NULL)
+    sstrncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
+  sstrncpy (vl.plugin_instance, plugin_instance, sizeof (vl.plugin_instance));
+
+  plugin_dispatch_values (type, &vl);
+} /* }}} static void submit */
+
+static int powerdns_get_data_dgram (list_item_t *item, /* {{{ */
+    char **ret_buffer,
+    size_t *ret_buffer_size)
+{
+  int sd;
+  int status;
+
+  char temp[4096];
+  char *buffer = NULL;
+  size_t buffer_size = 0;
+
+  struct sockaddr_un sa_unix;
+
+  sd = socket (PF_UNIX, item->socktype, 0);
+  if (sd < 0)
+  {
+    FUNC_ERROR ("socket");
+    return (-1);
+  }
+
+  memset (&sa_unix, 0, sizeof (sa_unix));
+  sa_unix.sun_family = AF_UNIX;
+  strncpy (sa_unix.sun_path,
+      (local_sockpath != NULL) ? local_sockpath : PDNS_LOCAL_SOCKPATH,
+      sizeof (sa_unix.sun_path));
+  sa_unix.sun_path[sizeof (sa_unix.sun_path) - 1] = 0;
+
+  status = unlink (sa_unix.sun_path);
+  if ((status != 0) && (errno != ENOENT))
+  {
+    FUNC_ERROR ("unlink");
+    close (sd);
+    return (-1);
+  }
+
+  do /* while (0) */
+  {
+    /* We need to bind to a specific path, because this is a datagram socket
+     * and otherwise the daemon cannot answer. */
+    status = bind (sd, (struct sockaddr *) &sa_unix, sizeof (sa_unix));
+    if (status != 0)
+    {
+      FUNC_ERROR ("bind");
+      break;
+    }
+
+    /* Make the socket writeable by the daemon.. */
+    status = chmod (sa_unix.sun_path, 0666);
+    if (status != 0)
+    {
+      FUNC_ERROR ("chmod");
+      break;
+    }
+
+    status = connect (sd, (struct sockaddr *) &item->sockaddr,
+        sizeof (item->sockaddr));
+    if (status != 0)
+    {
+      FUNC_ERROR ("connect");
+      break;
+    }
+
+    status = send (sd, item->command, strlen (item->command), 0);
+    if (status < 0)
+    {
+      FUNC_ERROR ("send");
+      break;
+    }
+
+    status = recv (sd, temp, sizeof (temp), /* flags = */ 0);
+    if (status < 0)
+    {
+      FUNC_ERROR ("recv");
+      break;
+    }
+    buffer_size = status + 1;
+    status = 0;
+  } while (0);
+
+  close (sd);
+  unlink (sa_unix.sun_path);
+
+  if (status != 0)
+    return (-1);
+
+  assert (buffer_size > 0);
+  buffer = (char *) malloc (buffer_size);
+  if (buffer == NULL)
+  {
+    FUNC_ERROR ("malloc");
+    return (-1);
+  }
+
+  memcpy (buffer, temp, buffer_size - 1);
+  buffer[buffer_size - 1] = 0;
+
+  *ret_buffer = buffer;
+  *ret_buffer_size = buffer_size;
+
+  return (0);
+} /* }}} int powerdns_get_data_dgram */
+
+static int powerdns_get_data_stream (list_item_t *item, /* {{{ */
+    char **ret_buffer,
+    size_t *ret_buffer_size)
+{
+  int sd;
+  int status;
+
+  char temp[4096];
+  char *buffer = NULL;
+  size_t buffer_size = 0;
+
+  sd = socket (PF_UNIX, item->socktype, 0);
+  if (sd < 0)
+  {
+    FUNC_ERROR ("socket");
+    return (-1);
+  }
+
+  status = connect (sd, (struct sockaddr *) &item->sockaddr,
+      sizeof (item->sockaddr));
+  if (status != 0)
+  {
+    FUNC_ERROR ("connect");
+    close (sd);
+    return (-1);
+  }
+
+  /* strlen + 1, because we need to send the terminating NULL byte, too. */
+  status = send (sd, item->command, strlen (item->command) + 1,
+      /* flags = */ 0);
+  if (status < 0)
+  {
+    FUNC_ERROR ("send");
+    close (sd);
+    return (-1);
+  }
+
+  while (42)
+  {
+    char *buffer_new;
+
+    status = recv (sd, temp, sizeof (temp), /* flags = */ 0);
+    if (status < 0)
+    {
+      FUNC_ERROR ("recv");
+      break;
+    }
+    else if (status == 0)
+      break;
+
+    buffer_new = (char *) realloc (buffer, buffer_size + status + 1);
+    if (buffer_new == NULL)
+    {
+      FUNC_ERROR ("realloc");
+      status = -1;
+      break;
+    }
+    buffer = buffer_new;
+
+    memcpy (buffer + buffer_size, temp, status);
+    buffer_size += status;
+    buffer[buffer_size] = 0;
+  } /* while (42) */
+  close (sd);
+  sd = -1;
+
+  if (status < 0)
+  {
+    sfree (buffer);
+  }
+  else
+  {
+    assert (status == 0);
+    *ret_buffer = buffer;
+    *ret_buffer_size = buffer_size;
+  }
+
+  return (status);
+} /* }}} int powerdns_get_data_stream */
+
+static int powerdns_get_data (list_item_t *item, char **ret_buffer,
+    size_t *ret_buffer_size)
+{
+  if (item->socktype == SOCK_DGRAM)
+    return (powerdns_get_data_dgram (item, ret_buffer, ret_buffer_size));
+  else if (item->socktype == SOCK_STREAM)
+    return (powerdns_get_data_stream (item, ret_buffer, ret_buffer_size));
+  else
+  {
+    ERROR ("powerdns plugin: Unknown socket type: %i", (int) item->socktype);
+    return (-1);
+  }
+} /* int powerdns_get_data */
+
+static int powerdns_read_server (list_item_t *item) /* {{{ */
+{
+  char *buffer = NULL;
+  size_t buffer_size = 0;
+  int status;
+
+  char *dummy;
+  char *saveptr;
+
+  char *key;
+  char *value;
+
+  const char* const *fields;
+  int fields_num;
+
+  if (item->command == NULL)
+    item->command = strdup ("SHOW *");
+  if (item->command == NULL)
+  {
+    ERROR ("powerdns plugin: strdup failed.");
+    return (-1);
+  }
+
+  status = powerdns_get_data (item, &buffer, &buffer_size);
+  if (status != 0)
+    return (-1);
+
+  if (item->fields_num != 0)
+  {
+    fields = (const char* const *) item->fields;
+    fields_num = item->fields_num;
+  }
+  else
+  {
+    fields = default_server_fields;
+    fields_num = default_server_fields_num;
+  }
+
+  assert (fields != NULL);
+  assert (fields_num > 0);
+
+  /* corrupt-packets=0,deferred-cache-inserts=0,deferred-cache-lookup=0,latency=0,packetcache-hit=0,packetcache-miss=0,packetcache-size=0,qsize-q=0,query-cache-hit=0,query-cache-miss=0,recursing-answers=0,recursing-questions=0,servfail-packets=0,tcp-answers=0,tcp-queries=0,timedout-packets=0,udp-answers=0,udp-queries=0,udp4-answers=0,udp4-queries=0,udp6-answers=0,udp6-queries=0, */
+  dummy = buffer;
+  saveptr = NULL;
+  while ((key = strtok_r (dummy, ",", &saveptr)) != NULL)
+  {
+    int i;
+
+    dummy = NULL;
+
+    value = strchr (key, '=');
+    if (value == NULL)
+      break;
+
+    *value = '\0';
+    value++;
+
+    if (value[0] == '\0')
+      continue;
+
+    /* Check if this item was requested. */
+    for (i = 0; i < fields_num; i++)
+      if (strcasecmp (key, fields[i]) == 0)
+       break;
+    if (i >= fields_num)
+      continue;
+
+    submit (item->instance, key, value);
+  } /* while (strtok_r) */
+
+  sfree (buffer);
+
+  return (0);
+} /* }}} int powerdns_read_server */
+
+/*
+ * powerdns_update_recursor_command
+ *
+ * Creates a string that holds the command to be sent to the recursor. This
+ * string is stores in the `command' member of the `list_item_t' passed to the
+ * function. This function is called by `powerdns_read_recursor'.
+ */
+static int powerdns_update_recursor_command (list_item_t *li) /* {{{ */
+{
+  char buffer[4096];
+  int status;
+
+  if (li == NULL)
+    return (0);
+
+  if (li->fields_num < 1)
+  {
+    sstrncpy (buffer, RECURSOR_COMMAND, sizeof (buffer));
+  }
+  else
+  {
+    sstrncpy (buffer, "get ", sizeof (buffer));
+    status = strjoin (&buffer[4], sizeof (buffer) - strlen ("get "),
+       li->fields, li->fields_num,
+       /* seperator = */ " ");
+    if (status < 0)
+    {
+      ERROR ("powerdns plugin: strjoin failed.");
+      return (-1);
+    }
+  }
+
+  buffer[sizeof (buffer) - 1] = 0;
+  li->command = strdup (buffer);
+  if (li->command == NULL)
+  {
+    ERROR ("powerdns plugin: strdup failed.");
+    return (-1);
+  }
+
+  return (0);
+} /* }}} int powerdns_update_recursor_command */
+
+static int powerdns_read_recursor (list_item_t *item) /* {{{ */
+{
+  char *buffer = NULL;
+  size_t buffer_size = 0;
+  int status;
+
+  char *dummy;
+
+  char *keys_list;
+  char *key;
+  char *key_saveptr;
+  char *value;
+  char *value_saveptr;
+
+  if (item->command == NULL)
+  {
+    status = powerdns_update_recursor_command (item);
+    if (status != 0)
+    {
+      ERROR ("powerdns plugin: powerdns_update_recursor_command failed.");
+      return (-1);
+    }
+
+    DEBUG ("powerdns plugin: powerdns_read_recursor: item->command = %s;",
+        item->command);
+  }
+  assert (item->command != NULL);
+
+  status = powerdns_get_data (item, &buffer, &buffer_size);
+  if (status != 0)
+  {
+    ERROR ("powerdns plugin: powerdns_get_data failed.");
+    return (-1);
+  }
+
+  keys_list = strdup (item->command);
+  if (keys_list == NULL)
+  {
+    FUNC_ERROR ("strdup");
+    sfree (buffer);
+    return (-1);
+  }
+
+  key_saveptr = NULL;
+  value_saveptr = NULL;
+
+  /* Skip the `get' at the beginning */
+  strtok_r (keys_list, " \t", &key_saveptr);
+
+  dummy = buffer;
+  while ((value = strtok_r (dummy, " \t\n\r", &value_saveptr)) != NULL)
+  {
+    dummy = NULL;
+
+    key = strtok_r (NULL, " \t", &key_saveptr);
+    if (key == NULL)
+      break;
+
+    submit (item->instance, key, value);
+  } /* while (strtok_r) */
+
+  sfree (buffer);
+  sfree (keys_list);
+
+  return (0);
+} /* }}} int powerdns_read_recursor */
+
+static int powerdns_config_add_string (const char *name, /* {{{ */
+    char **dest,
+    oconfig_item_t *ci)
+{
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("powerdns plugin: `%s' needs exactly one string argument.",
+       name);
+    return (-1);
+  }
+
+  sfree (*dest);
+  *dest = strdup (ci->values[0].value.string);
+  if (*dest == NULL)
+    return (-1);
+
+  return (0);
+} /* }}} int powerdns_config_add_string */
+
+static int powerdns_config_add_collect (list_item_t *li, /* {{{ */
+    oconfig_item_t *ci)
+{
+  int i;
+  char **temp;
+
+  if (ci->values_num < 1)
+  {
+    WARNING ("powerdns plugin: The `Collect' option needs "
+       "at least one argument.");
+    return (-1);
+  }
+
+  for (i = 0; i < ci->values_num; i++)
+    if (ci->values[i].type != OCONFIG_TYPE_STRING)
+    {
+      WARNING ("powerdns plugin: Only string arguments are allowed to "
+         "the `Collect' option.");
+      return (-1);
+    }
+
+  temp = (char **) realloc (li->fields,
+      sizeof (char *) * (li->fields_num + ci->values_num));
+  if (temp == NULL)
+  {
+    WARNING ("powerdns plugin: realloc failed.");
+    return (-1);
+  }
+  li->fields = temp;
+
+  for (i = 0; i < ci->values_num; i++)
+  {
+    li->fields[li->fields_num] = strdup (ci->values[i].value.string);
+    if (li->fields[li->fields_num] == NULL)
+    {
+      WARNING ("powerdns plugin: strdup failed.");
+      continue;
+    }
+    li->fields_num++;
+  }
+
+  /* Invalidate a previously computed command */
+  sfree (li->command);
+
+  return (0);
+} /* }}} int powerdns_config_add_collect */
+
+static int powerdns_config_add_server (oconfig_item_t *ci) /* {{{ */
+{
+  char *socket_temp;
+
+  list_item_t *item;
+  int status;
+  int i;
+
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("powerdns plugin: `%s' needs exactly one string argument.",
+       ci->key);
+    return (-1);
+  }
+
+  item = (list_item_t *) malloc (sizeof (list_item_t));
+  if (item == NULL)
+  {
+    ERROR ("powerdns plugin: malloc failed.");
+    return (-1);
+  }
+  memset (item, '\0', sizeof (list_item_t));
+
+  item->instance = strdup (ci->values[0].value.string);
+  if (item->instance == NULL)
+  {
+    ERROR ("powerdns plugin: strdup failed.");
+    sfree (item);
+    return (-1);
+  }
+
+  /*
+   * Set default values for the members of list_item_t
+   */
+  if (strcasecmp ("Server", ci->key) == 0)
+  {
+    item->server_type = SRV_AUTHORITATIVE;
+    item->func = powerdns_read_server;
+    item->socktype = SOCK_STREAM;
+    socket_temp = strdup (SERVER_SOCKET);
+  }
+  else if (strcasecmp ("Recursor", ci->key) == 0)
+  {
+    item->server_type = SRV_RECURSOR;
+    item->func = powerdns_read_recursor;
+    item->socktype = SOCK_DGRAM;
+    socket_temp = strdup (RECURSOR_SOCKET);
+  }
+  else
+  {
+    /* We must never get here.. */
+    assert (0);
+    return (-1);
+  }
+
+  status = 0;
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *option = ci->children + i;
+
+    if (strcasecmp ("Collect", option->key) == 0)
+      status = powerdns_config_add_collect (item, option);
+    else if (strcasecmp ("Socket", option->key) == 0)
+      status = powerdns_config_add_string ("Socket", &socket_temp, option);
+    else
+    {
+      ERROR ("powerdns plugin: Option `%s' not allowed here.", option->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  }
+
+  while (status == 0)
+  {
+    llentry_t *e;
+
+    if (socket_temp == NULL)
+    {
+      ERROR ("powerdns plugin: socket_temp == NULL.");
+      status = -1;
+      break;
+    }
+
+    item->sockaddr.sun_family = AF_UNIX;
+    sstrncpy (item->sockaddr.sun_path, socket_temp, UNIX_PATH_MAX);
+
+    e = llentry_create (item->instance, item);
+    if (e == NULL)
+    {
+      ERROR ("powerdns plugin: llentry_create failed.");
+      status = -1;
+      break;
+    }
+    llist_append (list, e);
+
+    break;
+  }
+
+  if (status != 0)
+  {
+    sfree (item);
+    return (-1);
+  }
+
+  DEBUG ("powerdns plugin: Add server: instance = %s;", item->instance);
+
+  return (0);
+} /* }}} int powerdns_config_add_server */
+
+static int powerdns_config (oconfig_item_t *ci) /* {{{ */
+{
+  int i;
+
+  DEBUG ("powerdns plugin: powerdns_config (ci = %p);", (void *) ci);
+
+  if (list == NULL)
+  {
+    list = llist_create ();
+
+    if (list == NULL)
+    {
+      ERROR ("powerdns plugin: `llist_create' failed.");
+      return (-1);
+    }
+  }
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *option = ci->children + i;
+
+    if ((strcasecmp ("Server", option->key) == 0)
+       || (strcasecmp ("Recursor", option->key) == 0))
+      powerdns_config_add_server (option);
+    else if (strcasecmp ("LocalSocket", option->key) == 0)
+    {
+      char *temp = strdup (option->key);
+      if (temp == NULL)
+        return (1);
+      sfree (local_sockpath);
+      local_sockpath = temp;
+    }
+    else
+    {
+      ERROR ("powerdns plugin: Option `%s' not allowed here.", option->key);
+    }
+  } /* for (i = 0; i < ci->children_num; i++) */
+
+  return (0);
+} /* }}} int powerdns_config */
+
+static int powerdns_read (void)
+{
+  llentry_t *e;
+
+  for (e = llist_head (list); e != NULL; e = e->next)
+  {
+    list_item_t *item = e->value;
+    item->func (item);
+  }
+
+  return (0);
+} /* static int powerdns_read */
+
+static int powerdns_shutdown (void)
+{
+  llentry_t *e;
+
+  if (list == NULL)
+    return (0);
+
+  for (e = llist_head (list); e != NULL; e = e->next)
+  {
+    list_item_t *item = (list_item_t *) e->value;
+    e->value = NULL;
+
+    sfree (item->instance);
+    sfree (item->command);
+    sfree (item);
+  }
+
+  llist_destroy (list);
+  list = NULL;
+
+  return (0);
+} /* static int powerdns_shutdown */
+
+void module_register (void)
+{
+  plugin_register_complex_config ("powerdns", powerdns_config);
+  plugin_register_read ("powerdns", powerdns_read);
+  plugin_register_shutdown ("powerdns", powerdns_shutdown );
+} /* void module_register */
+
+/* vim: set sw=2 sts=2 ts=8 fdm=marker : */
index 161037c..3b2c54f 100644 (file)
@@ -195,7 +195,7 @@ static int rra_get (char ***ret, const value_list_t *vl)
                span = rts[i];
 
                if ((span / ss) < rrarows)
-                       continue;
+                       span = ss * rrarows;
 
                if (cdp_len == 0)
                        cdp_len = 1;
@@ -947,6 +947,20 @@ static int rrd_write (const data_set_t *ds, const value_list_t *vl)
        return (status);
 } /* int rrd_write */
 
+static int rrd_flush (const int timeout)
+{
+       pthread_mutex_lock (&cache_lock);
+
+       if (cache == NULL) {
+               pthread_mutex_unlock (&cache_lock);
+               return (0);
+       }
+
+       rrd_cache_flush (timeout);
+       pthread_mutex_unlock (&cache_lock);
+       return (0);
+} /* int rrd_flush */
+
 static int rrd_config (const char *key, const char *value)
 {
        if (strcasecmp ("CacheTimeout", key) == 0)
@@ -1152,5 +1166,6 @@ void module_register (void)
                        config_keys, config_keys_num);
        plugin_register_init ("rrdtool", rrd_init);
        plugin_register_write ("rrdtool", rrd_write);
+       plugin_register_flush ("rrdtool", rrd_flush);
        plugin_register_shutdown ("rrdtool", rrd_shutdown);
 }
index 173ed9c..0968e31 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * collectd - src/sensors.c
- * Copyright (C) 2005-2007  Florian octo Forster
+ * Copyright (C) 2005-2008  Florian octo Forster
  *
  * 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
diff --git a/src/tail.c b/src/tail.c
new file mode 100644 (file)
index 0000000..01bf629
--- /dev/null
@@ -0,0 +1,353 @@
+/**
+ * collectd - src/tail.c
+ * Copyright (C) 2008  Florian octo Forster
+ *
+ * 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; only version 2 of the License is applicable.
+ *
+ * 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 St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "utils_tail_match.h"
+
+/*
+ *  <Plugin tail>
+ *    <File "/var/log/exim4/mainlog">
+ *     Instance "exim"
+ *     <Match>
+ *       Regex "S=([1-9][0-9]*)"
+ *       DSType "CounterAdd"
+ *       Type "ipt_bytes"
+ *       Instance "total"
+ *     </Match>
+ *    </File>
+ *  </Plugin>
+ */
+
+struct ctail_config_match_s
+{
+  char *regex;
+  int flags;
+  char *type;
+  char *type_instance;
+};
+typedef struct ctail_config_match_s ctail_config_match_t;
+
+cu_tail_match_t **tail_match_list = NULL;
+size_t tail_match_list_num = 0;
+
+static int ctail_config_add_string (const char *name, char **dest, oconfig_item_t *ci)
+{
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("tail plugin: `%s' needs exactly one string argument.", name);
+    return (-1);
+  }
+
+  sfree (*dest);
+  *dest = strdup (ci->values[0].value.string);
+  if (*dest == NULL)
+    return (-1);
+
+  return (0);
+} /* int ctail_config_add_string */
+
+static int ctail_config_add_match_dstype (ctail_config_match_t *cm,
+    oconfig_item_t *ci)
+{
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("tail plugin: `DSType' needs exactly one string argument.");
+    return (-1);
+  }
+
+  if (strncasecmp ("Gauge", ci->values[0].value.string, strlen ("Gauge")) == 0)
+  {
+    cm->flags = UTILS_MATCH_DS_TYPE_GAUGE;
+    if (strcasecmp ("GaugeAverage", ci->values[0].value.string) == 0)
+      cm->flags |= UTILS_MATCH_CF_GAUGE_AVERAGE;
+    else if (strcasecmp ("GaugeMin", ci->values[0].value.string) == 0)
+      cm->flags |= UTILS_MATCH_CF_GAUGE_MIN;
+    else if (strcasecmp ("GaugeMax", ci->values[0].value.string) == 0)
+      cm->flags |= UTILS_MATCH_CF_GAUGE_MAX;
+    else if (strcasecmp ("GaugeLast", ci->values[0].value.string) == 0)
+      cm->flags |= UTILS_MATCH_CF_GAUGE_LAST;
+    else
+      cm->flags = 0;
+  }
+  else if (strncasecmp ("Counter", ci->values[0].value.string, strlen ("Counter")) == 0)
+  {
+    cm->flags = UTILS_MATCH_DS_TYPE_COUNTER;
+    if (strcasecmp ("CounterSet", ci->values[0].value.string) == 0)
+      cm->flags |= UTILS_MATCH_CF_COUNTER_SET;
+    else if (strcasecmp ("CounterAdd", ci->values[0].value.string) == 0)
+      cm->flags |= UTILS_MATCH_CF_COUNTER_ADD;
+    else if (strcasecmp ("CounterInc", ci->values[0].value.string) == 0)
+      cm->flags |= UTILS_MATCH_CF_COUNTER_INC;
+    else
+      cm->flags = 0;
+  }
+  else
+  {
+    cm->flags = 0;
+  }
+
+  if (cm->flags == 0)
+  {
+    WARNING ("tail plugin: `%s' is not a valid argument to `DSType'.",
+       ci->values[0].value.string);
+    return (-1);
+  }
+
+  return (0);
+} /* int ctail_config_add_match_dstype */
+
+static int ctail_config_add_match (cu_tail_match_t *tm,
+    const char *plugin_instance, oconfig_item_t *ci)
+{
+  ctail_config_match_t cm;
+  int status;
+  int i;
+
+  memset (&cm, '\0', sizeof (cm));
+
+  if (ci->values_num != 0)
+  {
+    WARNING ("tail plugin: Ignoring arguments for the `Match' block.");
+  }
+
+  status = 0;
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *option = ci->children + i;
+
+    if (strcasecmp ("Regex", option->key) == 0)
+      status = ctail_config_add_string ("Regex", &cm.regex, option);
+    else if (strcasecmp ("DSType", option->key) == 0)
+      status = ctail_config_add_match_dstype (&cm, option);
+    else if (strcasecmp ("Type", option->key) == 0)
+      status = ctail_config_add_string ("Type", &cm.type, option);
+    else if (strcasecmp ("Instance", option->key) == 0)
+      status = ctail_config_add_string ("Instance", &cm.type_instance, option);
+    else
+    {
+      WARNING ("tail plugin: Option `%s' not allowed here.", option->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  } /* for (i = 0; i < ci->children_num; i++) */
+
+  while (status == 0)
+  {
+    if (cm.regex == NULL)
+    {
+      WARNING ("tail plugin: `Regex' missing in `Match' block.");
+      status = -1;
+      break;
+    }
+
+    if (cm.type == NULL)
+    {
+      WARNING ("tail plugin: `Type' missing in `Match' block.");
+      status = -1;
+      break;
+    }
+
+    if (cm.flags == 0)
+    {
+      WARNING ("tail plugin: `DSType' missing in `Match' block.");
+      status = -1;
+      break;
+    }
+
+    break;
+  } /* while (status == 0) */
+
+  if (status == 0)
+  {
+    status = tail_match_add_match_simple (tm, cm.regex, cm.flags,
+       "tail", plugin_instance, cm.type, cm.type_instance);
+
+    if (status != 0)
+    {
+      ERROR ("tail plugin: tail_match_add_match_simple failed.");
+    }
+  }
+
+  sfree (cm.regex);
+  sfree (cm.type);
+  sfree (cm.type_instance);
+
+  return (status);
+} /* int ctail_config_add_match */
+
+static int ctail_config_add_file (oconfig_item_t *ci)
+{
+  cu_tail_match_t *tm;
+  char *plugin_instance = NULL;
+  int num_matches = 0;
+  int status;
+  int i;
+
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("tail plugin: `File' needs exactly one string argument.");
+    return (-1);
+  }
+
+  tm = tail_match_create (ci->values[0].value.string);
+  if (tm == NULL)
+  {
+    ERROR ("tail plugin: tail_match_create (%s) failed.",
+       ci->values[0].value.string);
+    return (-1);
+  }
+
+  status = 0;
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *option = ci->children + i;
+
+    if (strcasecmp ("Match", option->key) == 0)
+    {
+      status = ctail_config_add_match (tm, plugin_instance, option);
+      if (status == 0)
+       num_matches++;
+      /* Be mild with failed matches.. */
+      status = 0;
+    }
+    else if (strcasecmp ("Instance", option->key) == 0)
+      status = ctail_config_add_string ("Instance", &plugin_instance, option);
+    else
+    {
+      WARNING ("tail plugin: Option `%s' not allowed here.", option->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  } /* for (i = 0; i < ci->children_num; i++) */
+
+  if (num_matches == 0)
+  {
+    ERROR ("tail plugin: No (valid) matches found for file `%s'.",
+       ci->values[0].value.string);
+    tail_match_destroy (tm);
+    return (-1);
+  }
+  else
+  {
+    cu_tail_match_t **temp;
+
+    temp = (cu_tail_match_t **) realloc (tail_match_list,
+       sizeof (cu_tail_match_t *) * (tail_match_list_num + 1));
+    if (temp == NULL)
+    {
+      ERROR ("tail plugin: realloc failed.");
+      tail_match_destroy (tm);
+      return (-1);
+    }
+
+    tail_match_list = temp;
+    tail_match_list[tail_match_list_num] = tm;
+    tail_match_list_num++;
+  }
+
+  return (0);
+} /* int ctail_config_add_file */
+
+static int ctail_config (oconfig_item_t *ci)
+{
+  int i;
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *option = ci->children + i;
+
+    if (strcasecmp ("File", option->key) == 0)
+      ctail_config_add_file (option);
+    else
+    {
+      WARNING ("tail plugin: Option `%s' not allowed here.", option->key);
+    }
+  } /* for (i = 0; i < ci->children_num; i++) */
+
+  return (0);
+} /* int ctail_config */
+
+static int ctail_init (void)
+{
+  if (tail_match_list_num == 0)
+  {
+    WARNING ("tail plugin: File list is empty. Returning an error.");
+    return (-1);
+  }
+
+  return (0);
+} /* int ctail_init */
+
+static int ctail_read (void)
+{
+  int success = 0;
+  int i;
+
+  for (i = 0; i < tail_match_list_num; i++)
+  {
+    int status;
+
+    status = tail_match_read (tail_match_list[i]);
+    if (status != 0)
+    {
+      ERROR ("tail plugin: tail_match_read[%i] failed.", i);
+    }
+    else
+    {
+      success++;
+    }
+  }
+
+  if (success == 0)
+    return (-1);
+  return (0);
+} /* int ctail_read */
+
+static int ctail_shutdown (void)
+{
+  int i;
+
+  for (i = 0; i < tail_match_list_num; i++)
+  {
+    tail_match_destroy (tail_match_list[i]);
+    tail_match_list[i] = NULL;
+  }
+  sfree (tail_match_list);
+  tail_match_list_num = 0;
+
+  return (0);
+} /* int ctail_shutdown */
+
+void module_register (void)
+{
+  plugin_register_complex_config ("tail", ctail_config);
+  plugin_register_init ("tail", ctail_init);
+  plugin_register_read ("tail", ctail_read);
+  plugin_register_shutdown ("tail", ctail_shutdown);
+} /* void module_register */
+
+/* vim: set sw=2 sts=2 ts=8 : */
diff --git a/src/teamspeak2.c b/src/teamspeak2.c
new file mode 100644 (file)
index 0000000..52a1007
--- /dev/null
@@ -0,0 +1,844 @@
+/**
+ * collectd - src/teamspeak2.c
+ * Copyright (C) 2008  Stefan Hacker
+ * Copyright (C) 2008  Florian Forster
+ *
+ * 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; only version 2 of the License is applicable.
+ *
+ * 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 St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Stefan Hacker <d0t at dbclan dot de>
+ *   Florian Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+
+/*
+ * Defines
+ */
+/* Default host and port */
+#define DEFAULT_HOST   "127.0.0.1"
+#define DEFAULT_PORT   "51234"
+
+/*
+ * Variables
+ */
+/* Server linked list structure */
+typedef struct vserver_list_s
+{
+       int port;
+       struct vserver_list_s *next;
+} vserver_list_t;
+static vserver_list_t *server_list = NULL;
+
+/* Host data */
+static char *config_host = NULL;
+static char *config_port = NULL;
+
+static FILE *global_read_fh = NULL;
+static FILE *global_write_fh = NULL;
+
+/* Config data */
+static const char *config_keys[] =
+{
+       "Host",
+       "Port",
+       "Server"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+
+/*
+ * Functions
+ */
+static int tss2_add_vserver (int vserver_port)
+{
+       /*
+        * Adds a new vserver to the linked list
+        */
+       vserver_list_t *entry;
+
+       /* Check port range */
+       if ((vserver_port <= 0) || (vserver_port > 65535))
+       {
+               ERROR ("teamspeak2 plugin: VServer port is invalid: %i",
+                               vserver_port);
+               return (-1);
+       }
+
+       /* Allocate memory */
+       entry = (vserver_list_t *) malloc (sizeof (vserver_list_t));
+       if (entry == NULL)
+       {
+               ERROR ("teamspeak2 plugin: malloc failed.");
+               return (-1);
+       }
+       memset (entry, 0, sizeof (vserver_list_t));
+
+       /* Save data */
+       entry->port = vserver_port;
+
+       /* Insert to list */
+       if(server_list == NULL) {
+               /* Add the server as the first element */
+               server_list = entry;
+       }
+       else {
+               vserver_list_t *prev;
+
+               /* Add the server to the end of the list */
+               prev = server_list;
+               while (prev->next != NULL)
+                       prev = prev->next;
+               prev->next = entry;
+       }
+
+       INFO ("teamspeak2 plugin: Registered new vserver: %i", vserver_port);
+
+       return (0);
+} /* int tss2_add_vserver */
+
+static void tss2_submit_gauge (const char *plugin_instance,
+               const char *type, const char *type_instance,
+               gauge_t value)
+{
+       /*
+        * Submits a gauge value to the collectd daemon
+        */
+       value_t values[1];
+       value_list_t vl = VALUE_LIST_INIT;
+
+       values[0].gauge = value;
+
+       vl.values     = values;
+       vl.values_len = 1;
+       vl.time       = time (NULL);
+       sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+       sstrncpy (vl.plugin, "teamspeak2", sizeof (vl.plugin));
+
+       if (plugin_instance != NULL)
+               sstrncpy (vl.plugin_instance, plugin_instance,
+                               sizeof (vl.plugin_instance));
+       
+       if (type_instance != NULL)
+               sstrncpy (vl.type_instance, type_instance,
+                               sizeof (vl.type_instance));
+       
+       plugin_dispatch_values (type, &vl);
+} /* void tss2_submit_gauge */
+
+static void tss2_submit_io (const char *plugin_instance, const char *type,
+               counter_t rx, counter_t tx)
+{
+       /*
+        * Submits the io rx/tx tuple to the collectd daemon
+        */
+       value_t values[2];
+       value_list_t vl = VALUE_LIST_INIT;
+
+       values[0].counter = rx;
+       values[1].counter = tx;
+
+       vl.values     = values;
+       vl.values_len = 2;
+       vl.time       = time (NULL);
+       sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+       sstrncpy (vl.plugin, "teamspeak2", sizeof (vl.plugin));
+
+       if (plugin_instance != NULL)
+               sstrncpy (vl.plugin_instance, plugin_instance,
+                               sizeof (vl.plugin_instance));
+       
+       plugin_dispatch_values (type, &vl);
+} /* void tss2_submit_gauge */
+
+static void tss2_close_socket (void)
+{
+       /*
+        * Closes all sockets
+        */
+       if (global_write_fh != NULL)
+       {
+               fputs ("quit\r\n", global_write_fh);
+       }
+
+       if (global_read_fh != NULL)
+       {
+               fclose (global_read_fh);
+               global_read_fh = NULL;
+       }
+
+       if (global_write_fh != NULL)
+       {
+               fclose (global_write_fh);
+               global_write_fh = NULL;
+       }
+} /* void tss2_close_socket */
+
+static int tss2_get_socket (FILE **ret_read_fh, FILE **ret_write_fh)
+{
+       /*
+        * Returns connected file objects or establishes the connection
+        * if it's not already present
+        */
+       struct addrinfo ai_hints;
+       struct addrinfo *ai_head;
+       struct addrinfo *ai_ptr;
+       int sd = -1;
+       int status;
+
+       /* Check if we already got opened connections */
+       if ((global_read_fh != NULL) && (global_write_fh != NULL))
+       {
+               /* If so, use them */
+               if (ret_read_fh != NULL)
+                       *ret_read_fh = global_read_fh;
+               if (ret_write_fh != NULL)
+                       *ret_write_fh = global_write_fh;
+               return (0);
+       }
+
+       /* Get all addrs for this hostname */
+       memset (&ai_hints, 0, sizeof (ai_hints));
+#ifdef AI_ADDRCONFIG
+       ai_hints.ai_flags |= AI_ADDRCONFIG;
+#endif
+       ai_hints.ai_family = AF_UNSPEC;
+       ai_hints.ai_socktype = SOCK_STREAM;
+
+       status = getaddrinfo ((config_host != NULL) ? config_host : DEFAULT_HOST,
+                       (config_port != NULL) ? config_port : DEFAULT_PORT,
+                       &ai_hints,
+                       &ai_head);
+       if (status != 0)
+       {
+               ERROR ("teamspeak2 plugin: getaddrinfo failed: %s",
+                               gai_strerror (status));
+               return (-1);
+       }
+
+       /* Try all given hosts until we can connect to one */
+       for (ai_ptr = ai_head; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next)
+       {
+               /* Create socket */
+               sd = socket (ai_ptr->ai_family, ai_ptr->ai_socktype,
+                               ai_ptr->ai_protocol);
+               if (sd < 0)
+               {
+                       char errbuf[1024];
+                       WARNING ("teamspeak2 plugin: socket failed: %s",
+                                       sstrerror (errno, errbuf, sizeof (errbuf)));
+                       continue;
+               }
+
+               /* Try to connect */
+               status = connect (sd, ai_ptr->ai_addr, ai_ptr->ai_addrlen);
+               if (status != 0)
+               {
+                       char errbuf[1024];
+                       WARNING ("teamspeak2 plugin: connect failed: %s",
+                                       sstrerror (errno, errbuf, sizeof (errbuf)));
+                       close (sd);
+                       continue;
+               }
+
+               /*
+                * Success, we can break. Don't need more than one connection
+                */
+               break;
+       } /* for (ai_ptr) */
+
+       freeaddrinfo (ai_head);
+
+       /* Check if we really got connected */
+       if (sd < 0)
+               return (-1);
+
+       /* Create file objects from sockets */
+       global_read_fh = fdopen (sd, "r");
+       if (global_read_fh == NULL)
+       {
+               char errbuf[1024];
+               ERROR ("teamspeak2 plugin: fdopen failed: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               close (sd);
+               return (-1);
+       }
+
+       global_write_fh = fdopen (sd, "w");
+       if (global_write_fh == NULL)
+       {
+               char errbuf[1024];
+               ERROR ("teamspeak2 plugin: fdopen failed: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               tss2_close_socket ();
+               return (-1);
+       }
+
+       { /* Check that the server correctly identifies itself. */
+               char buffer[4096];
+               char *buffer_ptr;
+
+               buffer_ptr = fgets (buffer, sizeof (buffer), global_read_fh);
+               buffer[sizeof (buffer) - 1] = 0;
+
+               if (memcmp ("[TS]\r\n", buffer, 6) != 0)
+               {
+                       ERROR ("teamspeak2 plugin: Unexpected response when connecting "
+                                       "to server. Expected ``[TS]'', got ``%s''.",
+                                       buffer);
+                       tss2_close_socket ();
+                       return (-1);
+               }
+               DEBUG ("teamspeak2 plugin: Server send correct banner, connected!");
+       }
+
+       /* Copy the new filehandles to the given pointers */
+       if (ret_read_fh != NULL)
+               *ret_read_fh = global_read_fh;
+       if (ret_write_fh != NULL)
+               *ret_write_fh = global_write_fh;
+       return (0);
+} /* int tss2_get_socket */
+
+static int tss2_send_request (FILE *fh, const char *request)
+{
+       /*
+        * This function puts a request to the server socket
+        */
+       int status;
+
+       status = fputs (request, fh);
+       if (status < 0)
+       {
+               ERROR ("teamspeak2 plugin: fputs failed.");
+               tss2_close_socket ();
+               return (-1);
+       }
+       fflush (fh);
+
+       return (0);
+} /* int tss2_send_request */
+
+static int tss2_receive_line (FILE *fh, char *buffer, int buffer_size)
+{
+       /*
+        * Receive a single line from the given file object
+        */
+       char *temp;
+        
+       /*
+        * fgets is blocking but much easier then doing anything else
+        * TODO: Non-blocking Version would be safer
+        */
+       temp = fgets (buffer, buffer_size, fh);
+       if (temp == NULL)
+       {
+               char errbuf[1024];
+               ERROR ("teamspeak2 plugin: fgets failed: %s",
+                               sstrerror (errno, errbuf, sizeof(errbuf)));
+               tss2_close_socket ();
+               return (-1);
+       }
+
+       buffer[buffer_size - 1] = 0;
+       return (0);
+} /* int tss2_receive_line */
+
+static int tss2_select_vserver (FILE *read_fh, FILE *write_fh, vserver_list_t *vserver)
+{
+       /*
+        * Tell the server to select the given vserver
+        */
+       char command[128];
+       char response[128];
+       int status;
+
+       /* Send request */
+       snprintf (command, sizeof (command), "sel %i\r\n", vserver->port);
+       command[sizeof (command) - 1] = 0;
+
+       status = tss2_send_request (write_fh, command);
+       if (status != 0)
+       {
+               ERROR ("teamspeak2 plugin: tss2_send_request (%s) failed.", command);
+               return (-1);
+       }
+
+       /* Get answer */
+       status = tss2_receive_line (read_fh, response, sizeof (response));
+       if (status != 0)
+       {
+               ERROR ("teamspeak2 plugin: tss2_receive_line failed.");
+               return (-1);
+       }
+       response[sizeof (response) - 1] = 0;
+
+       /* Check answer */
+       if ((strncasecmp ("OK", response, 2) == 0)
+                       && ((response[2] == 0)
+                               || (response[2] == '\n')
+                               || (response[2] == '\r')))
+               return (0);
+
+       ERROR ("teamspeak2 plugin: Command ``%s'' failed. "
+                       "Response received from server was: ``%s''.",
+                       command, response);
+       return (-1);
+} /* int tss2_select_vserver */
+
+static int tss2_vserver_gapl (FILE *read_fh, FILE *write_fh,
+               vserver_list_t *vserver, gauge_t *ret_value)
+{
+       /*
+        * Reads the vserver's average packet loss and submits it to collectd.
+        * Be sure to run the tss2_read_vserver function before calling this so
+        * the vserver is selected correctly.
+        */
+       gauge_t packet_loss = NAN;
+       int status;
+
+       status = tss2_send_request (write_fh, "gapl\r\n");
+       if (status != 0)
+       {
+               ERROR("teamspeak2 plugin: tss2_send_request (gapl) failed.");
+               return (-1);
+       }
+
+       while (42)
+       {
+               char buffer[4096];
+               char *value;
+               char *endptr = NULL;
+               
+               status = tss2_receive_line (read_fh, buffer, sizeof (buffer));
+               if (status != 0)
+               {
+                       /* Set to NULL just to make sure noone uses these FHs anymore. */
+                       read_fh = NULL;
+                       write_fh = NULL;
+                       ERROR ("teamspeak2 plugin: tss2_receive_line failed.");
+                       return (-1);
+               }
+               buffer[sizeof (buffer) - 1] = 0;
+               
+               if (strncmp ("average_packet_loss=", buffer,
+                                       strlen ("average_packet_loss=")) == 0)
+               {
+                       /* Got average packet loss, now interpret it */
+                       value = &buffer[20];
+                       /* Replace , with . */
+                       while (*value != 0)
+                       {
+                               if (*value == ',')
+                               {
+                                       *value = '.';
+                                       break;
+                               }
+                               value++;
+                       }
+                       
+                       value = &buffer[20];
+                       
+                       packet_loss = strtod (value, &endptr);
+                       if (value == endptr)
+                       {
+                               /* Failed */
+                               WARNING ("teamspeak2 plugin: Could not read average package "
+                                               "loss from string: %s", buffer);
+                               continue;
+                       }
+               }
+               else if (strncasecmp ("OK", buffer, 2) == 0)
+               {
+                       break;
+               }
+               else if (strncasecmp ("ERROR", buffer, 5) == 0)
+               {
+                       ERROR ("teamspeak2 plugin: Server returned an error: %s", buffer);
+                       return (-1);
+               }
+               else
+               {
+                       WARNING ("teamspeak2 plugin: Server returned unexpected string: %s",
+                                       buffer);
+               }
+       }
+       
+       *ret_value = packet_loss;
+       return (0);
+} /* int tss2_vserver_gapl */
+
+static int tss2_read_vserver (vserver_list_t *vserver)
+{
+       /*
+        * Poll information for the given vserver and submit it to collect.
+        * If vserver is NULL the global server information will be queried.
+        */
+       int status;
+
+       gauge_t users = NAN;
+       gauge_t channels = NAN;
+       gauge_t servers = NAN;
+       counter_t rx_octets = 0;
+       counter_t tx_octets = 0;
+       counter_t rx_packets = 0;
+       counter_t tx_packets = 0;
+       gauge_t packet_loss = NAN;
+       int valid = 0;
+
+       char plugin_instance[DATA_MAX_NAME_LEN];
+
+       FILE *read_fh;
+       FILE *write_fh;
+
+       /* Get the send/receive sockets */
+       status = tss2_get_socket (&read_fh, &write_fh);
+       if (status != 0)
+       {
+               ERROR ("teamspeak2 plugin: tss2_get_socket failed.");
+               return (-1);
+       }
+
+       if (vserver == NULL)
+       {
+               /* Request global information */
+               memset (plugin_instance, 0, sizeof (plugin_instance));
+
+               status = tss2_send_request (write_fh, "gi\r\n");
+       }
+       else
+       {
+               /* Request server information */
+               snprintf (plugin_instance, sizeof (plugin_instance), "vserver%i",
+                               vserver->port);
+               plugin_instance[sizeof (plugin_instance) - 1] = 0;
+
+               /* Select the server */
+               status = tss2_select_vserver (read_fh, write_fh, vserver);
+               if (status != 0)
+                       return (status);
+
+               status = tss2_send_request (write_fh, "si\r\n");
+       }
+
+       if (status != 0)
+       {
+               ERROR ("teamspeak2 plugin: tss2_send_request failed.");
+               return (-1);
+       }
+
+       /* Loop until break */
+       while (42)
+       {
+               char buffer[4096];
+               char *key;
+               char *value;
+               char *endptr = NULL;
+               
+               /* Read one line of the server's answer */
+               status = tss2_receive_line (read_fh, buffer, sizeof (buffer));
+               if (status != 0)
+               {
+                       /* Set to NULL just to make sure noone uses these FHs anymore. */
+                       read_fh = NULL;
+                       write_fh = NULL;
+                       ERROR ("teamspeak2 plugin: tss2_receive_line failed.");
+                       break;
+               }
+
+               if (strncasecmp ("ERROR", buffer, 5) == 0)
+               {
+                       ERROR ("teamspeak2 plugin: Server returned an error: %s",
+                                       buffer);
+                       break;
+               }
+               else if (strncasecmp ("OK", buffer, 2) == 0)
+               {
+                       break;
+               }
+
+               /* Split line into key and value */
+               key = strchr (buffer, '_');
+               if (key == NULL)
+               {
+                       DEBUG ("teamspeak2 plugin: Cannot parse line: %s", buffer);
+                       continue;
+               }
+               key++;
+
+               /* Evaluate assignment */
+               value = strchr (key, '=');
+               if (value == NULL)
+               {
+                       DEBUG ("teamspeak2 plugin: Cannot parse line: %s", buffer);
+                       continue;
+               }
+               *value = 0;
+               value++;
+
+               /* Check for known key and save the given value */
+               /* global info: users_online,
+                * server info: currentusers. */
+               if ((strcmp ("currentusers", key) == 0)
+                               || (strcmp ("users_online", key) == 0))
+               {
+                       users = strtod (value, &endptr);
+                       if (value != endptr)
+                               valid |= 0x01;
+               }
+               /* global info: channels,
+                * server info: currentchannels. */
+               else if ((strcmp ("currentchannels", key) == 0)
+                               || (strcmp ("channels", key) == 0))
+               {
+                       channels = strtod (value, &endptr);
+                       if (value != endptr)
+                               valid |= 0x40;
+               }
+               /* global only */
+               else if (strcmp ("servers", key) == 0)
+               {
+                       servers = strtod (value, &endptr);
+                       if (value != endptr)
+                               valid |= 0x80;
+               }
+               else if (strcmp ("bytesreceived", key) == 0)
+               {
+                       rx_octets = strtoll (value, &endptr, 0);
+                       if (value != endptr)
+                               valid |= 0x02;
+               }
+               else if (strcmp ("bytessend", key) == 0)
+               {
+                       tx_octets = strtoll (value, &endptr, 0);
+                       if (value != endptr)
+                               valid |= 0x04;
+               }
+               else if (strcmp ("packetsreceived", key) == 0)
+               {
+                       rx_packets = strtoll (value, &endptr, 0);
+                       if (value != endptr)
+                               valid |= 0x08;
+               }
+               else if (strcmp ("packetssend", key) == 0)
+               {
+                       tx_packets = strtoll (value, &endptr, 0);
+                       if (value != endptr)
+                               valid |= 0x10;
+               }
+               else if ((strncmp ("allow_codec_", key, strlen ("allow_codec_")) == 0)
+                               || (strncmp ("bwinlast", key, strlen ("bwinlast")) == 0)
+                               || (strncmp ("bwoutlast", key, strlen ("bwoutlast")) == 0)
+                               || (strncmp ("webpost_", key, strlen ("webpost_")) == 0)
+                               || (strcmp ("adminemail", key) == 0)
+                               || (strcmp ("clan_server", key) == 0)
+                               || (strcmp ("countrynumber", key) == 0)
+                               || (strcmp ("id", key) == 0)
+                               || (strcmp ("ispname", key) == 0)
+                               || (strcmp ("linkurl", key) == 0)
+                               || (strcmp ("maxusers", key) == 0)
+                               || (strcmp ("name", key) == 0)
+                               || (strcmp ("password", key) == 0)
+                               || (strcmp ("platform", key) == 0)
+                               || (strcmp ("server_platform", key) == 0)
+                               || (strcmp ("server_uptime", key) == 0)
+                               || (strcmp ("server_version", key) == 0)
+                               || (strcmp ("udpport", key) == 0)
+                               || (strcmp ("uptime", key) == 0)
+                               || (strcmp ("users_maximal", key) == 0)
+                               || (strcmp ("welcomemessage", key) == 0))
+                       /* ignore */;
+               else
+               {
+                       INFO ("teamspeak2 plugin: Unknown key-value-pair: "
+                                       "key = %s; value = %s;", key, value);
+               }
+       } /* while (42) */
+
+       /* Collect vserver packet loss rates only if the loop above did not exit
+        * with an error. */
+       if ((status == 0) && (vserver != NULL))
+       {
+               status = tss2_vserver_gapl (read_fh, write_fh, vserver, &packet_loss);
+               if (status == 0)
+               {
+                       valid |= 0x20;
+               }
+               else
+               {
+                       WARNING ("teamspeak2 plugin: Reading package loss "
+                                       "for vserver %i failed.", vserver->port);
+               }
+       }
+
+       if ((valid & 0x01) == 0x01)
+               tss2_submit_gauge (plugin_instance, "users", NULL, users);
+
+       if ((valid & 0x06) == 0x06)
+               tss2_submit_io (plugin_instance, "io_octets", rx_octets, tx_octets);
+
+       if ((valid & 0x18) == 0x18)
+               tss2_submit_io (plugin_instance, "io_packets", rx_packets, tx_packets);
+
+       if ((valid & 0x20) == 0x20)
+               tss2_submit_gauge (plugin_instance, "percent", "packet_loss", packet_loss);
+
+       if ((valid & 0x40) == 0x40)
+               tss2_submit_gauge (plugin_instance, "gauge", "channels", channels);
+
+       if ((valid & 0x80) == 0x80)
+               tss2_submit_gauge (plugin_instance, "gauge", "servers", servers);
+
+       if (valid == 0)
+               return (-1);
+       return (0);
+} /* int tss2_read_vserver */
+
+static int tss2_config (const char *key, const char *value)
+{
+       /*
+        * Interpret configuration values
+        */
+    if (strcasecmp ("Host", key) == 0)
+       {
+               char *temp;
+
+               temp = strdup (value);
+               if (temp == NULL)
+               {
+                       ERROR("teamspeak2 plugin: strdup failed.");
+                       return (1);
+               }
+               sfree (config_host);
+               config_host = temp;
+       }
+       else if (strcasecmp ("Port", key) == 0)
+       {
+               char *temp;
+
+               temp = strdup (value);
+               if (temp == NULL)
+               {
+                       ERROR("teamspeak2 plugin: strdup failed.");
+                       return (1);
+               }
+               sfree (config_port);
+               config_port = temp;
+       }
+       else if (strcasecmp ("Server", key) == 0)
+       {
+               /* Server variable found */
+               int status;
+               
+               status = tss2_add_vserver (atoi (value));
+               if (status != 0)
+                       return (1);
+       }
+       else
+       {
+               /* Unknown variable found */
+               return (-1);
+       }
+
+       return 0;
+} /* int tss2_config */
+
+static int tss2_read (void)
+{
+       /*
+        * Poll function which collects global and vserver information
+        * and submits it to collectd
+        */
+       vserver_list_t *vserver;
+       int success = 0;
+       int status;
+
+       /* Handle global server variables */
+       status = tss2_read_vserver (NULL);
+       if (status == 0)
+       {
+               success++;
+       }
+       else
+       {
+               WARNING ("teamspeak2 plugin: Reading global server variables failed.");
+       }
+
+       /* Handle vservers */
+       for (vserver = server_list; vserver != NULL; vserver = vserver->next)
+       {
+               status = tss2_read_vserver (vserver);
+               if (status == 0)
+               {
+                       success++;
+               }
+               else
+               {
+                       WARNING ("teamspeak2 plugin: Reading statistics "
+                                       "for vserver %i failed.", vserver->port);
+                       continue;
+               }
+       }
+       
+       if (success == 0)
+               return (-1);
+    return (0);
+} /* int tss2_read */
+
+static int tss2_shutdown(void)
+{
+       /*
+        * Shutdown handler
+        */
+       vserver_list_t *entry;
+
+       tss2_close_socket ();
+
+       entry = server_list;
+       server_list = NULL;
+       while (entry != NULL)
+       {
+               vserver_list_t *next;
+
+               next = entry->next;
+               sfree (entry);
+               entry = next;
+       }
+
+       /* Get rid of the configuration */
+       sfree (config_host);
+       sfree (config_port);
+       
+    return (0);
+} /* int tss2_shutdown */
+
+void module_register(void)
+{
+       /*
+        * Mandatory module_register function
+        */
+       plugin_register_config ("teamspeak2", tss2_config,
+                       config_keys, config_keys_num);
+       plugin_register_read ("teamspeak2", tss2_read);
+       plugin_register_shutdown ("teamspeak2", tss2_shutdown);
+} /* void module_register */
+
+/* vim: set sw=4 ts=4 : */
index a19f26d..97c170b 100644 (file)
@@ -3,6 +3,8 @@ apache_connections      count:GAUGE:0:65535
 apache_requests                count:COUNTER:0:134217728
 apache_scoreboard      count:GAUGE:0:65535
 bitrate                        value:GAUGE:0:4294967295
+cache_result           value:COUNTER:0:4294967295
+cache_size              value:GAUGE:0:4294967295
 charge                 value:GAUGE:0:U
 connections            value:COUNTER:0:U
 counter                        value:COUNTER:U:U
@@ -15,9 +17,11 @@ disk_merged          read:COUNTER:0:4294967295, write:COUNTER:0:4294967295
 disk_octets            read:COUNTER:0:17179869183, write:COUNTER:0:17179869183
 disk_ops               read:COUNTER:0:4294967295, write:COUNTER:0:4294967295
 disk_time              read:COUNTER:0:1000000, write:COUNTER:0:1000000
+dns_answer             value:COUNTER:0:65535
 dns_octets             queries:COUNTER:0:125000000, responses:COUNTER:0:125000000
 dns_opcode             value:COUNTER:0:65535
 dns_qtype              value:COUNTER:0:65535
+dns_question           value:COUNTER:0:65535
 dns_rcode              value:COUNTER:0:65535
 email_check            value:GAUGE:0:U
 email_count            value:GAUGE:0:U
@@ -36,9 +40,12 @@ if_octets            rx:COUNTER:0:4294967295, tx:COUNTER:0:4294967295
 if_packets             rx:COUNTER:0:4294967295, tx:COUNTER:0:4294967295
 if_rx_errors           value:COUNTER:0:4294967295
 if_tx_errors           value:COUNTER:0:4294967295
+io_octets              rx:COUNTER:0:4294967295, tx:COUNTER:0:4294967295
+io_packets             rx:COUNTER:0:4294967295, tx:COUNTER:0:4294967295
 ipt_bytes              value:COUNTER:0:134217728
 ipt_packets            value:COUNTER:0:134217728
 irq                    value:COUNTER:U:65535
+latency                        value:GAUGE:0:65535
 load                   shortterm:GAUGE:0:100, midterm:GAUGE:0:100, longterm:GAUGE:0:100
 memcached_command      value:COUNTER:0:U
 memcached_connections  value:GAUGE:0:U
@@ -57,6 +64,7 @@ nginx_connections     value:GAUGE:0:U
 nginx_requests         value:COUNTER:0:134217728
 percent                        percent:GAUGE:0:100.1
 ping                   ping:GAUGE:0:65535
+players                        value:GAUGE:0:1000000
 power                  value:GAUGE:0:U
 ps_count               processes:GAUGE:0:1000000, threads:GAUGE:0:1000000
 ps_cputime             user:COUNTER:0:16000000, syst:COUNTER:0:16000000
@@ -77,6 +85,10 @@ time_offset          seconds:GAUGE:-1000000:1000000
 users                  users:GAUGE:0:65535
 virt_cpu_total         ns:COUNTER:0:256000000000
 virt_vcpu              ns:COUNTER:0:1000000000
+vmpage_action          value:COUNTER:0:4294967295
+vmpage_faults          minflt:COUNTER:0:9223372036854775807, majflt:COUNTER:0:9223372036854775807
+vmpage_io              in:COUNTER:0:4294967295, out:COUNTER:0:4294967295
+vmpage_number          value:GAUGE:0:4294967295
 voltage_threshold      value:GAUGE:U:U, threshold:GAUGE:U:U
 voltage                        value:GAUGE:U:U
 vs_memory              value:GAUGE:0:9223372036854775807
index ff84262..3be792d 100644 (file)
@@ -85,9 +85,6 @@ static int parse_ds (data_source_t *dsrc, char *buf, size_t buf_len)
   else
     dsrc->max = atof (fields[3]);
 
-  DEBUG ("parse_ds: dsrc = {%s, %i, %lf, %lf};",
-      dsrc->name, dsrc->type, dsrc->min, dsrc->max);
-
   return (0);
 } /* int parse_ds */
 
@@ -125,9 +122,6 @@ static void parse_line (char *buf)
       return;
     }
 
-  DEBUG ("parse_line: ds = {%s, %i, %p};",
-      ds->type, ds->ds_num, (void *) ds->ds);
-
   plugin_register_data_set (ds);
 
   sfree (ds->ds);
index 45ed9c6..d80091b 100644 (file)
@@ -24,6 +24,9 @@
 #include "plugin.h"
 #include "configfile.h"
 
+#include "utils_cmd_flush.h"
+#include "utils_cmd_getval.h"
+#include "utils_cmd_listval.h"
 #include "utils_cmd_putval.h"
 #include "utils_cmd_putnotif.h"
 
 #define US_DEFAULT_PATH LOCALSTATEDIR"/run/"PACKAGE_NAME"-unixsock"
 
 /*
- * Private data structures
- */
-/* linked list of cached values */
-typedef struct value_cache_s
-{
-       char       name[4*DATA_MAX_NAME_LEN];
-       int        values_num;
-       gauge_t   *gauge;
-       counter_t *counter;
-       const data_set_t *ds;
-       time_t     time;
-       struct value_cache_s *next;
-} value_cache_t;
-
-/*
  * Private variables
  */
 /* valid configuration file keys */
@@ -65,10 +53,9 @@ static const char *config_keys[] =
 {
        "SocketFile",
        "SocketGroup",
-       "SocketPerms",
-       NULL
+       "SocketPerms"
 };
-static int config_keys_num = 3;
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
 
 static int loop = 0;
 
@@ -80,261 +67,9 @@ static int   sock_perms = S_IRWXU | S_IRWXG;
 
 static pthread_t listen_thread = (pthread_t) 0;
 
-/* Linked list and auxilliary variables for saving values */
-static value_cache_t   *cache_head = NULL;
-static pthread_mutex_t  cache_lock = PTHREAD_MUTEX_INITIALIZER;
-static time_t           cache_oldest = -1;
-
 /*
  * Functions
  */
-static value_cache_t *cache_search (const char *name)
-{
-       value_cache_t *vc;
-
-       for (vc = cache_head; vc != NULL; vc = vc->next)
-       {
-               if (strcmp (vc->name, name) == 0)
-                       break;
-       } /* for vc = cache_head .. NULL */
-
-       return (vc);
-} /* value_cache_t *cache_search */
-
-static int cache_insert (const data_set_t *ds, const value_list_t *vl)
-{
-       /* We're called from `cache_update' so we don't need to lock the mutex */
-       value_cache_t *vc;
-       int i;
-
-       DEBUG ("unixsock plugin: cache_insert: ds->type = %s; ds->ds_num = %i;"
-                       " vl->values_len = %i;",
-                       ds->type, ds->ds_num, vl->values_len);
-#if COLLECT_DEBUG
-       assert (ds->ds_num == vl->values_len);
-#else
-       if (ds->ds_num != vl->values_len)
-       {
-               ERROR ("unixsock plugin: ds->type = %s: (ds->ds_num = %i) != "
-                               "(vl->values_len = %i)",
-                               ds->type, ds->ds_num, vl->values_len);
-               return (-1);
-       }
-#endif
-
-       vc = (value_cache_t *) malloc (sizeof (value_cache_t));
-       if (vc == NULL)
-       {
-               char errbuf[1024];
-               pthread_mutex_unlock (&cache_lock);
-               ERROR ("unixsock plugin: malloc failed: %s",
-                               sstrerror (errno, errbuf, sizeof (errbuf)));
-               return (-1);
-       }
-
-       vc->gauge = (gauge_t *) malloc (sizeof (gauge_t) * vl->values_len);
-       if (vc->gauge == NULL)
-       {
-               char errbuf[1024];
-               pthread_mutex_unlock (&cache_lock);
-               ERROR ("unixsock plugin: malloc failed: %s",
-                               sstrerror (errno, errbuf, sizeof (errbuf)));
-               free (vc);
-               return (-1);
-       }
-
-       vc->counter = (counter_t *) malloc (sizeof (counter_t) * vl->values_len);
-       if (vc->counter == NULL)
-       {
-               char errbuf[1024];
-               pthread_mutex_unlock (&cache_lock);
-               ERROR ("unixsock plugin: malloc failed: %s",
-                               sstrerror (errno, errbuf, sizeof (errbuf)));
-               free (vc->gauge);
-               free (vc);
-               return (-1);
-       }
-
-       if (FORMAT_VL (vc->name, sizeof (vc->name), vl, ds))
-       {
-               pthread_mutex_unlock (&cache_lock);
-               ERROR ("unixsock plugin: FORMAT_VL failed.");
-               free (vc->counter);
-               free (vc->gauge);
-               free (vc);
-               return (-1);
-       }
-
-       for (i = 0; i < ds->ds_num; i++)
-       {
-               if (ds->ds[i].type == DS_TYPE_COUNTER)
-               {
-                       vc->gauge[i] = 0.0;
-                       vc->counter[i] = vl->values[i].counter;
-               }
-               else if (ds->ds[i].type == DS_TYPE_GAUGE)
-               {
-                       vc->gauge[i] = vl->values[i].gauge;
-                       vc->counter[i] = 0;
-               }
-               else
-               {
-                       vc->gauge[i] = 0.0;
-                       vc->counter[i] = 0;
-               }
-       }
-       vc->values_num = ds->ds_num;
-       vc->ds = ds;
-
-       vc->next = cache_head;
-       cache_head = vc;
-
-       vc->time = vl->time;
-       if ((vc->time < cache_oldest) || (-1 == cache_oldest))
-               cache_oldest = vc->time;
-
-       pthread_mutex_unlock (&cache_lock);
-       return (0);
-} /* int cache_insert */
-
-static int cache_update (const data_set_t *ds, const value_list_t *vl)
-{
-       char name[4*DATA_MAX_NAME_LEN];;
-       value_cache_t *vc;
-       int i;
-
-       if (FORMAT_VL (name, sizeof (name), vl, ds) != 0)
-               return (-1);
-
-       pthread_mutex_lock (&cache_lock);
-
-       vc = cache_search (name);
-
-       /* pthread_mutex_lock is called by cache_insert. */
-       if (vc == NULL)
-               return (cache_insert (ds, vl));
-
-       assert (vc->values_num == ds->ds_num);
-       assert (vc->values_num == vl->values_len);
-
-       /* Avoid floating-point exceptions due to division by zero. */
-       if (vc->time >= vl->time)
-       {
-               pthread_mutex_unlock (&cache_lock);
-               ERROR ("unixsock plugin: vc->time >= vl->time. vc->time = %u; "
-                               "vl->time = %u; vl = %s;",
-                               (unsigned int) vc->time, (unsigned int) vl->time,
-                               name);
-               return (-1);
-       } /* if (vc->time >= vl->time) */
-
-       /*
-        * Update the values. This is possibly a lot more that you'd expect
-        * because we honor min and max values and handle counter overflows here.
-        */
-       for (i = 0; i < ds->ds_num; i++)
-       {
-               if (ds->ds[i].type == DS_TYPE_COUNTER)
-               {
-                       if (vl->values[i].counter < vc->counter[i])
-                       {
-                               if (vl->values[i].counter <= 4294967295U)
-                               {
-                                       vc->gauge[i] = ((4294967295U - vl->values[i].counter)
-                                                       + vc->counter[i]) / (vl->time - vc->time);
-                               }
-                               else
-                               {
-                                       vc->gauge[i] = ((18446744073709551615ULL - vl->values[i].counter)
-                                               + vc->counter[i]) / (vl->time - vc->time);
-                               }
-                       }
-                       else
-                       {
-                               vc->gauge[i] = (vl->values[i].counter - vc->counter[i])
-                                       / (vl->time - vc->time);
-                       }
-
-                       vc->counter[i] = vl->values[i].counter;
-               }
-               else if (ds->ds[i].type == DS_TYPE_GAUGE)
-               {
-                       vc->gauge[i] = vl->values[i].gauge;
-                       vc->counter[i] = 0;
-               }
-               else
-               {
-                       vc->gauge[i] = NAN;
-                       vc->counter[i] = 0;
-               }
-
-               if (isnan (vc->gauge[i])
-                               || (!isnan (ds->ds[i].min) && (vc->gauge[i] < ds->ds[i].min))
-                               || (!isnan (ds->ds[i].max) && (vc->gauge[i] > ds->ds[i].max)))
-                       vc->gauge[i] = NAN;
-       } /* for i = 0 .. ds->ds_num */
-
-       vc->ds = ds;
-       vc->time = vl->time;
-
-       if ((vc->time < cache_oldest) || (-1 == cache_oldest))
-               cache_oldest = vc->time;
-
-       pthread_mutex_unlock (&cache_lock);
-       return (0);
-} /* int cache_update */
-
-static void cache_flush (int max_age)
-{
-       value_cache_t *this;
-       value_cache_t *prev;
-       time_t now;
-
-       pthread_mutex_lock (&cache_lock);
-
-       now = time (NULL);
-
-       if ((now - cache_oldest) <= max_age)
-       {
-               pthread_mutex_unlock (&cache_lock);
-               return;
-       }
-       
-       cache_oldest = now;
-
-       prev = NULL;
-       this = cache_head;
-
-       while (this != NULL)
-       {
-               if ((now - this->time) <= max_age)
-               {
-                       if (this->time < cache_oldest)
-                               cache_oldest = this->time;
-
-                       prev = this;
-                       this = this->next;
-                       continue;
-               }
-
-               if (prev == NULL)
-                       cache_head = this->next;
-               else
-                       prev->next = this->next;
-
-               free (this->gauge);
-               free (this->counter);
-               free (this);
-
-               if (prev == NULL)
-                       this = cache_head;
-               else
-                       this = prev->next;
-       } /* while (this != NULL) */
-
-       pthread_mutex_unlock (&cache_lock);
-} /* void cache_flush */
-
 static int us_open_socket (void)
 {
        struct sockaddr_un sa;
@@ -420,130 +155,6 @@ static int us_open_socket (void)
        return (0);
 } /* int us_open_socket */
 
-static int us_handle_getval (FILE *fh, char **fields, int fields_num)
-{
-       char *hostname;
-       char *plugin;
-       char *plugin_instance;
-       char *type;
-       char *type_instance;
-       char  name[4*DATA_MAX_NAME_LEN];
-       value_cache_t *vc;
-       int   status;
-       int   i;
-
-       if (fields_num != 2)
-       {
-               DEBUG ("unixsock plugin: Wrong number of fields: %i", fields_num);
-               fprintf (fh, "-1 Wrong number of fields: Got %i, expected 2.\n",
-                               fields_num);
-               fflush (fh);
-               return (-1);
-       }
-       DEBUG ("unixsock plugin: Got query for `%s'", fields[1]);
-
-       status = parse_identifier (fields[1], &hostname,
-                       &plugin, &plugin_instance,
-                       &type, &type_instance);
-       if (status != 0)
-       {
-               DEBUG ("unixsock plugin: Cannot parse `%s'", fields[1]);
-               fprintf (fh, "-1 Cannot parse identifier.\n");
-               fflush (fh);
-               return (-1);
-       }
-
-       status = format_name (name, sizeof (name),
-                       hostname, plugin, plugin_instance, type, type_instance);
-       if (status != 0)
-       {
-               fprintf (fh, "-1 format_name failed.\n");
-               return (-1);
-       }
-
-       pthread_mutex_lock (&cache_lock);
-
-       DEBUG ("vc = cache_search (%s)", name);
-       vc = cache_search (name);
-
-       if (vc == NULL)
-       {
-               DEBUG ("Did not find cache entry.");
-               fprintf (fh, "-1 No such value");
-       }
-       else
-       {
-               DEBUG ("Found cache entry.");
-               fprintf (fh, "%i", vc->values_num);
-               for (i = 0; i < vc->values_num; i++)
-               {
-                       fprintf (fh, " %s=", vc->ds->ds[i].name);
-                       if (isnan (vc->gauge[i]))
-                               fprintf (fh, "NaN");
-                       else
-                               fprintf (fh, "%12e", vc->gauge[i]);
-               }
-       }
-
-       /* Free the mutex as soon as possible and definitely before flushing */
-       pthread_mutex_unlock (&cache_lock);
-
-       fprintf (fh, "\n");
-       fflush (fh);
-
-       return (0);
-} /* int us_handle_getval */
-
-static int us_handle_listval (FILE *fh, char **fields, int fields_num)
-{
-       char buffer[1024];
-       char **value_list = NULL;
-       int value_list_len = 0;
-       value_cache_t *entry;
-       int i;
-
-       if (fields_num != 1)
-       {
-               DEBUG ("unixsock plugin: us_handle_listval: "
-                               "Wrong number of fields: %i", fields_num);
-               fprintf (fh, "-1 Wrong number of fields: Got %i, expected 1.\n",
-                               fields_num);
-               fflush (fh);
-               return (-1);
-       }
-
-       pthread_mutex_lock (&cache_lock);
-
-       for (entry = cache_head; entry != NULL; entry = entry->next)
-       {
-               char **tmp;
-
-               snprintf (buffer, sizeof (buffer), "%u %s\n",
-                               (unsigned int) entry->time, entry->name);
-               buffer[sizeof (buffer) - 1] = '\0';
-               
-               tmp = realloc (value_list, sizeof (char *) * (value_list_len + 1));
-               if (tmp == NULL)
-                       continue;
-               value_list = tmp;
-
-               value_list[value_list_len] = strdup (buffer);
-
-               if (value_list[value_list_len] != NULL)
-                       value_list_len++;
-       } /* for (entry) */
-
-       pthread_mutex_unlock (&cache_lock);
-
-       DEBUG ("unixsock plugin: us_handle_listval: value_list_len = %i", value_list_len);
-       fprintf (fh, "%i Values found\n", value_list_len);
-       for (i = 0; i < value_list_len; i++)
-               fputs (value_list[i], fh);
-       fflush (fh);
-
-       return (0);
-} /* int us_handle_listval */
-
 static void *us_handle_client (void *arg)
 {
        int fd;
@@ -578,10 +189,34 @@ static void *us_handle_client (void *arg)
                pthread_exit ((void *) 1);
        }
 
-       while (fgets (buffer, sizeof (buffer), fhin) != NULL)
+       /* change output buffer to line buffered mode */
+       if (setvbuf (fhout, NULL, _IOLBF, 0) != 0)
+       {
+               char errbuf[1024];
+               ERROR ("unixsock plugin: setvbuf failed: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               fclose (fhin);
+               fclose (fhout);
+               pthread_exit ((void *) 1);
+       }
+
+       while (42)
        {
                int len;
 
+               errno = 0;
+               if (fgets (buffer, sizeof (buffer), fhin) == NULL)
+               {
+                       if (errno != 0)
+                       {
+                               char errbuf[1024];
+                               WARNING ("unixsock plugin: failed to read from socket #%i: %s",
+                                               fileno (fhin),
+                                               sstrerror (errno, errbuf, sizeof (errbuf)));
+                       }
+                       break;
+               }
+
                len = strlen (buffer);
                while ((len > 0)
                                && ((buffer[len - 1] == '\n') || (buffer[len - 1] == '\r')))
@@ -603,7 +238,7 @@ static void *us_handle_client (void *arg)
 
                if (strcasecmp (fields[0], "getval") == 0)
                {
-                       us_handle_getval (fhout, fields, fields_num);
+                       handle_getval (fhout, fields, fields_num);
                }
                else if (strcasecmp (fields[0], "putval") == 0)
                {
@@ -611,16 +246,26 @@ static void *us_handle_client (void *arg)
                }
                else if (strcasecmp (fields[0], "listval") == 0)
                {
-                       us_handle_listval (fhout, fields, fields_num);
+                       handle_listval (fhout, fields, fields_num);
                }
                else if (strcasecmp (fields[0], "putnotif") == 0)
                {
                        handle_putnotif (fhout, fields, fields_num);
                }
+               else if (strcasecmp (fields[0], "flush") == 0)
+               {
+                       handle_flush (fhout, fields, fields_num);
+               }
                else
                {
-                       fprintf (fhout, "-1 Unknown command: %s\n", fields[0]);
-                       fflush (fhout);
+                       if (fprintf (fhout, "-1 Unknown command: %s\n", fields[0]) < 0)
+                       {
+                               char errbuf[1024];
+                               WARNING ("unixsock plugin: failed to write to socket #%i: %s",
+                                               fileno (fhout),
+                                               sstrerror (errno, errbuf, sizeof (errbuf)));
+                               break;
+                       }
                }
        } /* while (fgets) */
 
@@ -707,13 +352,21 @@ static int us_config (const char *key, const char *val)
 {
        if (strcasecmp (key, "SocketFile") == 0)
        {
+               char *new_sock_file = strdup (val);
+               if (new_sock_file == NULL)
+                       return (1);
+
                sfree (sock_file);
-               sock_file = strdup (val);
+               sock_file = new_sock_file;
        }
        else if (strcasecmp (key, "SocketGroup") == 0)
        {
+               char *new_sock_group = strdup (val);
+               if (new_sock_group == NULL)
+                       return (1);
+
                sfree (sock_group);
-               sock_group = strdup (val);
+               sock_group = new_sock_group;
        }
        else if (strcasecmp (key, "SocketPerms") == 0)
        {
@@ -764,20 +417,11 @@ static int us_shutdown (void)
        return (0);
 } /* int us_shutdown */
 
-static int us_write (const data_set_t *ds, const value_list_t *vl)
-{
-       cache_update (ds, vl);
-       cache_flush (2 * interval_g);
-
-       return (0);
-}
-
 void module_register (void)
 {
        plugin_register_config ("unixsock", us_config,
                        config_keys, config_keys_num);
        plugin_register_init ("unixsock", us_init);
-       plugin_register_write ("unixsock", us_write);
        plugin_register_shutdown ("unixsock", us_shutdown);
 } /* void module_register (void) */
 
index 5e6a0b5..d0bd63c 100644 (file)
 #include "common.h"
 #include "plugin.h"
 
+#if HAVE_STATGRAB_H
+# include <statgrab.h>
+#endif /* HAVE_STATGRAB_H */
+
 #if HAVE_UTMPX_H
 # include <utmpx.h>
 /* #endif HAVE_UTMPX_H */
@@ -89,6 +93,16 @@ static int users_read (void)
        users_submit (users);
 /* #endif HAVE_GETUTENT */
 
+#elif HAVE_LIBSTATGRAB
+       sg_user_stats *us;
+
+       us = sg_get_user_stats ();
+       if (us == NULL)
+               return (-1);   
+
+       users_submit ((gauge_t) us->num_entries);
+/* #endif HAVE_LIBSTATGRAB */
+
 #else
 # error "No applicable input method."
 #endif
index b9b8962..9f7e3b6 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * collectd - src/utils_cache.c
- * Copyright (C) 2007  Florian octo Forster
+ * Copyright (C) 2007,2008  Florian octo Forster
  *
  * 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
@@ -460,41 +460,155 @@ int uc_update (const data_set_t *ds, const value_list_t *vl)
   return (0);
 } /* int uc_update */
 
-gauge_t *uc_get_rate (const data_set_t *ds, const value_list_t *vl)
+int uc_get_rate_by_name (const char *name, gauge_t **ret_values, size_t *ret_values_num)
 {
-  char name[6 * DATA_MAX_NAME_LEN];
   gauge_t *ret = NULL;
+  size_t ret_num = 0;
   cache_entry_t *ce = NULL;
-
-  if (FORMAT_VL (name, sizeof (name), vl, ds) != 0)
-  {
-    ERROR ("uc_get_rate: FORMAT_VL failed.");
-    return (NULL);
-  }
+  int status = 0;
 
   pthread_mutex_lock (&cache_lock);
 
   if (c_avl_get (cache_tree, name, (void *) &ce) == 0)
   {
     assert (ce != NULL);
-    assert (ce->values_num == ds->ds_num);
 
-    ret = (gauge_t *) malloc (ce->values_num * sizeof (gauge_t));
+    ret_num = ce->values_num;
+    ret = (gauge_t *) malloc (ret_num * sizeof (gauge_t));
     if (ret == NULL)
     {
-      ERROR ("uc_get_rate: malloc failed.");
+      ERROR ("utils_cache: uc_get_rate_by_name: malloc failed.");
+      status = -1;
     }
     else
     {
-      memcpy (ret, ce->values_gauge, ce->values_num * sizeof (gauge_t));
+      memcpy (ret, ce->values_gauge, ret_num * sizeof (gauge_t));
     }
   }
+  else
+  {
+    DEBUG ("utils_cache: uc_get_rate_by_name: No such value: %s", name);
+    status = -1;
+  }
 
   pthread_mutex_unlock (&cache_lock);
 
+  if (status == 0)
+  {
+    *ret_values = ret;
+    *ret_values_num = ret_num;
+  }
+
+  return (status);
+} /* gauge_t *uc_get_rate_by_name */
+
+gauge_t *uc_get_rate (const data_set_t *ds, const value_list_t *vl)
+{
+  char name[6 * DATA_MAX_NAME_LEN];
+  gauge_t *ret = NULL;
+  size_t ret_num = 0;
+  int status;
+
+  if (FORMAT_VL (name, sizeof (name), vl, ds) != 0)
+  {
+    ERROR ("uc_insert: FORMAT_VL failed.");
+    return (NULL);
+  }
+
+  status = uc_get_rate_by_name (name, &ret, &ret_num);
+  if (status != 0)
+    return (NULL);
+
+  /* This is important - the caller has no other way of knowing how many
+   * values are returned. */
+  if (ret_num != ds->ds_num)
+  {
+    ERROR ("utils_cache: uc_get_rate: ds[%s] has %i values, "
+       "but uc_get_rate_by_name returned %i.",
+       ds->type, ds->ds_num, ret_num);
+    sfree (ret);
+    return (NULL);
+  }
+
   return (ret);
 } /* gauge_t *uc_get_rate */
 
+int uc_get_names (char ***ret_names, time_t **ret_times, size_t *ret_number)
+{
+  c_avl_iterator_t *iter;
+  char *key;
+  cache_entry_t *value;
+
+  char **names = NULL;
+  time_t *times = NULL;
+  size_t number = 0;
+
+  int status = 0;
+
+  if ((ret_names == NULL) || (ret_number == NULL))
+    return (-1);
+
+  pthread_mutex_lock (&cache_lock);
+
+  iter = c_avl_get_iterator (cache_tree);
+  while (c_avl_iterator_next (iter, (void *) &key, (void *) &value) == 0)
+  {
+    char **temp;
+
+    if (ret_times != NULL)
+    {
+      time_t *tmp_times;
+
+      tmp_times = (time_t *) realloc (times, sizeof (time_t) * (number + 1));
+      if (tmp_times == NULL)
+      {
+       status = -1;
+       break;
+      }
+      times = tmp_times;
+      times[number] = value->last_time;
+    }
+
+    temp = (char **) realloc (names, sizeof (char *) * (number + 1));
+    if (temp == NULL)
+    {
+      status = -1;
+      break;
+    }
+    names = temp;
+    names[number] = strdup (key);
+    if (names[number] == NULL)
+    {
+      status = -1;
+      break;
+    }
+    number++;
+  } /* while (c_avl_iterator_next) */
+
+  c_avl_iterator_destroy (iter);
+  pthread_mutex_unlock (&cache_lock);
+
+  if (status != 0)
+  {
+    size_t i;
+    
+    for (i = 0; i < number; i++)
+    {
+      sfree (names[i]);
+    }
+    sfree (names);
+
+    return (-1);
+  }
+
+  *ret_names = names;
+  if (ret_times != NULL)
+    *ret_times = times;
+  *ret_number = number;
+
+  return (0);
+} /* int uc_get_names */
+
 int uc_get_state (const data_set_t *ds, const value_list_t *vl)
 {
   char name[6 * DATA_MAX_NAME_LEN];
index 51d9c03..a965feb 100644 (file)
 int uc_init (void);
 int uc_check_timeout (void);
 int uc_update (const data_set_t *ds, const value_list_t *vl);
+int uc_get_rate_by_name (const char *name, gauge_t **ret_values, size_t *ret_values_num);
 gauge_t *uc_get_rate (const data_set_t *ds, const value_list_t *vl);
 
+int uc_get_names (char ***ret_names, time_t **ret_times, size_t *ret_number);
+
 int uc_get_state (const data_set_t *ds, const value_list_t *vl);
 int uc_set_state (const data_set_t *ds, const value_list_t *vl, int state);
 
diff --git a/src/utils_cmd_flush.c b/src/utils_cmd_flush.c
new file mode 100644 (file)
index 0000000..6fa8b7b
--- /dev/null
@@ -0,0 +1,97 @@
+/**
+ * collectd - src/utils_cmd_flush.c
+ * Copyright (C) 2008  Sebastian Harl
+ * Copyright (C) 2008  Florian Forster
+ *
+ * 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; only version 2 of the License is applicable.
+ *
+ * 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 St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Sebastian "tokkee" Harl <sh at tokkee.org>
+ *   Florian "octo" Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#define print_to_socket(fh, ...) \
+       if (fprintf (fh, __VA_ARGS__) < 0) { \
+               char errbuf[1024]; \
+               WARNING ("handle_flush: failed to write to socket #%i: %s", \
+                               fileno (fh), sstrerror (errno, errbuf, sizeof (errbuf))); \
+               return -1; \
+       }
+
+int handle_flush (FILE *fh, char **fields, int fields_num)
+{
+       int success = 0;
+       int error   = 0;
+
+       int timeout = -1;
+
+       int i;
+
+       for (i = 1; i < fields_num; i++)
+       {
+               char *option = fields[i];
+               int   status = 0;
+
+               if (strncasecmp ("plugin=", option, strlen ("plugin=")) == 0)
+               {
+                       char *plugin = option + strlen ("plugin=");
+
+                       if (0 == plugin_flush_one (timeout, plugin))
+                               ++success;
+                       else
+                               ++error;
+               }
+               else if (strncasecmp ("timeout=", option, strlen ("timeout=")) == 0)
+               {
+                       char *endptr = NULL;
+                       char *value  = option + strlen ("timeout=");
+
+                       errno = 0;
+                       timeout = strtol (value, &endptr, 0);
+
+                       if ((endptr == value) || (0 != errno))
+                               status = -1;
+                       else if (0 >= timeout)
+                               timeout = -1;
+               }
+               else
+                       status = -1;
+
+               if (status != 0)
+               {
+                       print_to_socket (fh, "-1 Cannot parse option %s\n", option);
+                       return (-1);
+               }
+       }
+
+       if ((success + error) > 0)
+       {
+               print_to_socket (fh, "0 Done: %i successful, %i errors\n",
+                               success, error);
+       }
+       else
+       {
+               plugin_flush_all (timeout);
+               print_to_socket (fh, "0 Done\n");
+       }
+
+       return (0);
+} /* int handle_flush */
+
+/* vim: set sw=4 ts=4 tw=78 noexpandtab : */
+
diff --git a/src/utils_cmd_flush.h b/src/utils_cmd_flush.h
new file mode 100644 (file)
index 0000000..334f086
--- /dev/null
@@ -0,0 +1,30 @@
+/**
+ * collectd - src/utils_cmd_flush.h
+ * Copyright (C) 2008  Sebastian Harl
+ *
+ * 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; only version 2 of the License is applicable.
+ *
+ * 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 St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Author:
+ *   Sebastian "tokkee" Harl <sh at tokkee.org>
+ **/
+
+#ifndef UTILS_CMD_FLUSH_H
+#define UTILS_CMD_FLUSH_H 1
+
+int handle_flush (FILE *fh, char **fields, int fields_num);
+
+#endif /* UTILS_CMD_FLUSH_H */
+
+/* vim: set sw=4 ts=4 tw=78 noexpandtab : */
+
diff --git a/src/utils_cmd_getval.c b/src/utils_cmd_getval.c
new file mode 100644 (file)
index 0000000..470d302
--- /dev/null
@@ -0,0 +1,134 @@
+/**
+ * collectd - src/utils_cms_getval.c
+ * Copyright (C) 2008  Florian octo Forster
+ *
+ * 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; only version 2 of the License is applicable.
+ *
+ * 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 St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Author:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#include "utils_cache.h"
+
+#define print_to_socket(fh, ...) \
+  if (fprintf (fh, __VA_ARGS__) < 0) { \
+    char errbuf[1024]; \
+    WARNING ("handle_getval: failed to write to socket #%i: %s", \
+       fileno (fh), sstrerror (errno, errbuf, sizeof (errbuf))); \
+    return -1; \
+  }
+
+int handle_getval (FILE *fh, char **fields, int fields_num)
+{
+  char *hostname;
+  char *plugin;
+  char *plugin_instance;
+  char *type;
+  char *type_instance;
+  gauge_t *values;
+  size_t values_num;
+
+  char *identifier_copy;
+
+  const data_set_t *ds;
+
+  int   status;
+  int   i;
+
+  if (fields_num != 2)
+  {
+    DEBUG ("unixsock plugin: Wrong number of fields: %i", fields_num);
+    print_to_socket (fh, "-1 Wrong number of fields: Got %i, expected 2.\n",
+       fields_num);
+    return (-1);
+  }
+  DEBUG ("unixsock plugin: Got query for `%s'", fields[1]);
+
+  if (strlen (fields[1]) < strlen ("h/p/t"))
+  {
+    print_to_socket (fh, "-1 Invalied identifier, %s\n", fields[1]);
+    return (-1);
+  }
+
+  /* parse_identifier() modifies its first argument,
+   * returning pointers into it */
+  identifier_copy = sstrdup (fields[1]);
+
+  status = parse_identifier (identifier_copy, &hostname,
+      &plugin, &plugin_instance,
+      &type, &type_instance);
+  if (status != 0)
+  {
+    DEBUG ("unixsock plugin: Cannot parse `%s'", fields[1]);
+    print_to_socket (fh, "-1 Cannot parse identifier.\n");
+    sfree (identifier_copy);
+    return (-1);
+  }
+
+  ds = plugin_get_ds (type);
+  if (ds == NULL)
+  {
+    DEBUG ("unixsock plugin: plugin_get_ds (%s) == NULL;", type);
+    print_to_socket (fh, "-1 Type `%s' is unknown.\n", type);
+    sfree (identifier_copy);
+    return (-1);
+  }
+
+  values = NULL;
+  values_num = 0;
+  status = uc_get_rate_by_name (fields[1], &values, &values_num);
+  if (status != 0)
+  {
+    print_to_socket (fh, "-1 No such value\n");
+    sfree (identifier_copy);
+    return (-1);
+  }
+
+  if (ds->ds_num != values_num)
+  {
+    ERROR ("ds[%s]->ds_num = %i, "
+       "but uc_get_rate_by_name returned %u values.",
+       ds->type, ds->ds_num, (unsigned int) values_num);
+    print_to_socket (fh, "-1 Error reading value from cache.\n");
+    sfree (values);
+    sfree (identifier_copy);
+    return (-1);
+  }
+
+  print_to_socket (fh, "%u Value%s found\n", (unsigned int) values_num,
+      (values_num == 1) ? "" : "s");
+  for (i = 0; i < values_num; i++)
+  {
+    print_to_socket (fh, "%s=", ds->ds[i].name);
+    if (isnan (values[i]))
+    {
+      print_to_socket (fh, "NaN\n");
+    }
+    else
+    {
+      print_to_socket (fh, "%12e\n", values[i]);
+    }
+  }
+
+  sfree (values);
+  sfree (identifier_copy);
+
+  return (0);
+} /* int handle_getval */
+
+/* vim: set sw=2 sts=2 ts=8 : */
diff --git a/src/utils_cmd_getval.h b/src/utils_cmd_getval.h
new file mode 100644 (file)
index 0000000..d7bd115
--- /dev/null
@@ -0,0 +1,29 @@
+/**
+ * collectd - src/utils_cms_getval.h
+ * Copyright (C) 2008  Florian octo Forster
+ *
+ * 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; only version 2 of the License is applicable.
+ *
+ * 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 St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Author:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#ifndef UTILS_CMD_GETVAL_H
+#define UTILS_CMD_GETVAL_H 1
+
+int handle_getval (FILE *fh, char **fields, int fields_num);
+
+#endif /* UTILS_CMD_GETVAL_H */
+
+/* vim: set sw=2 sts=2 ts=8 : */
diff --git a/src/utils_cmd_listval.c b/src/utils_cmd_listval.c
new file mode 100644 (file)
index 0000000..6f03e75
--- /dev/null
@@ -0,0 +1,70 @@
+/**
+ * collectd - src/utils_cms_listval.c
+ * Copyright (C) 2008  Florian octo Forster
+ *
+ * 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; only version 2 of the License is applicable.
+ *
+ * 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 St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Author:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#include "utils_cmd_listval.h"
+#include "utils_cache.h"
+
+#define print_to_socket(fh, ...) \
+  if (fprintf (fh, __VA_ARGS__) < 0) { \
+    char errbuf[1024]; \
+    WARNING ("handle_listval: failed to write to socket #%i: %s", \
+       fileno (fh), sstrerror (errno, errbuf, sizeof (errbuf))); \
+    return -1; \
+  }
+
+int handle_listval (FILE *fh, char **fields, int fields_num)
+{
+  char **names = NULL;
+  time_t *times = NULL;
+  size_t number = 0;
+  size_t i;
+  int status;
+
+  if (fields_num != 1)
+  {
+    DEBUG ("command listval: us_handle_listval: Wrong number of fields: %i",
+       fields_num);
+    print_to_socket (fh, "-1 Wrong number of fields: Got %i, expected 1.\n",
+       fields_num);
+    return (-1);
+  }
+
+  status = uc_get_names (&names, &times, &number);
+  if (status != 0)
+  {
+    DEBUG ("command listval: uc_get_names failed with status %i", status);
+    print_to_socket (fh, "-1 uc_get_names failed.\n");
+    return (-1);
+  }
+
+  print_to_socket (fh, "%i Value%s found\n",
+      (int) number, (number == 1) ? "" : "s");
+  for (i = 0; i < number; i++)
+    print_to_socket (fh, "%u %s\n", (unsigned int) times[i], names[i]);
+
+  return (0);
+} /* int handle_listval */
+
+/* vim: set sw=2 sts=2 ts=8 : */
diff --git a/src/utils_cmd_listval.h b/src/utils_cmd_listval.h
new file mode 100644 (file)
index 0000000..c918796
--- /dev/null
@@ -0,0 +1,29 @@
+/**
+ * collectd - src/utils_cms_listval.h
+ * Copyright (C) 2008  Florian octo Forster
+ *
+ * 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; only version 2 of the License is applicable.
+ *
+ * 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 St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Author:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#ifndef UTILS_CMD_LISTVAL_H
+#define UTILS_CMD_LISTVAL_H 1
+
+int handle_listval (FILE *fh, char **fields, int fields_num);
+
+#endif /* UTILS_CMD_LISTVAL_H */
+
+/* vim: set sw=2 sts=2 ts=8 : */
index 18c1ece..eb7d60b 100644 (file)
 #include "common.h"
 #include "plugin.h"
 
+#define print_to_socket(fh, ...) \
+  if (fprintf (fh, __VA_ARGS__) < 0) { \
+    char errbuf[1024]; \
+    WARNING ("handle_putnotif: failed to write to socket #%i: %s", \
+       fileno (fh), sstrerror (errno, errbuf, sizeof (errbuf))); \
+    return -1; \
+  }
+
 static int parse_option_severity (notification_t *n, char *value)
 {
   if (strcasecmp (value, "Failure") == 0)
@@ -107,9 +115,9 @@ int handle_putnotif (FILE *fh, char **fields, int fields_num)
   if (fields_num < 4)
   {
     DEBUG ("cmd putnotif: Wrong number of fields: %i", fields_num);
-    fprintf (fh, "-1 Wrong number of fields: Got %i, expected at least 4.\n",
+    print_to_socket (fh, "-1 Wrong number of fields: Got %i, "
+       "expected at least 4.\n",
        fields_num);
-    fflush (fh);
     return (-1);
   }
 
@@ -123,7 +131,7 @@ int handle_putnotif (FILE *fh, char **fields, int fields_num)
       status = parse_message (&n, fields + i, fields_num - i);
       if (status != 0)
       {
-       fprintf (fh, "-1 Error parsing the message. Have you hit the "
+       print_to_socket (fh, "-1 Error parsing the message. Have you hit the "
            "limit of %u bytes?\n", (unsigned int) sizeof (n.message));
       }
       break;
@@ -133,7 +141,7 @@ int handle_putnotif (FILE *fh, char **fields, int fields_num)
       status = parse_option (&n, fields[i]);
       if (status != 0)
       {
-       fprintf (fh, "-1 Error parsing option `%s'\n", fields[i]);
+       print_to_socket (fh, "-1 Error parsing option `%s'\n", fields[i]);
        break;
       }
     }
@@ -142,17 +150,17 @@ int handle_putnotif (FILE *fh, char **fields, int fields_num)
   /* Check for required fields and complain if anything is missing. */
   if ((status == 0) && (n.severity == 0))
   {
-    fprintf (fh, "-1 Option `severity' missing.\n");
+    print_to_socket (fh, "-1 Option `severity' missing.\n");
     status = -1;
   }
   if ((status == 0) && (n.time == 0))
   {
-    fprintf (fh, "-1 Option `time' missing.\n");
+    print_to_socket (fh, "-1 Option `time' missing.\n");
     status = -1;
   }
   if ((status == 0) && (strlen (n.message) == 0))
   {
-    fprintf (fh, "-1 No message or message of length 0 given.\n");
+    print_to_socket (fh, "-1 No message or message of length 0 given.\n");
     status = -1;
   }
 
@@ -161,9 +169,8 @@ int handle_putnotif (FILE *fh, char **fields, int fields_num)
   if (status == 0)
   {
     plugin_dispatch_notification (&n);
-    fprintf (fh, "0 Success\n");
+    print_to_socket (fh, "0 Success\n");
   }
-  fflush (fh);
 
   return (0);
 } /* int handle_putnotif */
index a953172..08b3bb3 100644 (file)
@@ -22,8 +22,6 @@
 #ifndef UTILS_CMD_PUTNOTIF_H
 #define UTILS_CMD_PUTNOTIF_H 1
 
-#include "plugin.h"
-
 int handle_putnotif (FILE *fh, char **fields, int fields_num);
 
 /* vim: set shiftwidth=2 softtabstop=2 tabstop=8 : */
index e5f7495..16a09b1 100644 (file)
 #include "common.h"
 #include "plugin.h"
 
+#define print_to_socket(fh, ...) \
+       if (fprintf (fh, __VA_ARGS__) < 0) { \
+               char errbuf[1024]; \
+               WARNING ("handle_putval: failed to write to socket #%i: %s", \
+                               fileno (fh), sstrerror (errno, errbuf, sizeof (errbuf))); \
+               return -1; \
+       }
+
 static int parse_value (const data_set_t *ds, value_list_t *vl,
                const char *type,
                FILE *fh, char *buffer)
@@ -36,7 +44,7 @@ static int parse_value (const data_set_t *ds, value_list_t *vl,
        char *value_str = strchr (time_str, ':');
        if (value_str == NULL)
        {
-               fprintf (fh, "-1 No time found.\n");
+               print_to_socket (fh, "-1 No time found.\n");
                return (-1);
        }
        *value_str = '\0'; value_str++;
@@ -76,7 +84,7 @@ static int parse_value (const data_set_t *ds, value_list_t *vl,
                                "Number of values incorrect: "
                                "Got %i, expected %i. Identifier is `%s'.",
                                i, vl->values_len, identifier);
-               fprintf (fh, "-1 Number of values incorrect: "
+               print_to_socket (fh, "-1 Number of values incorrect: "
                                "Got %i, expected %i.\n",
                                i, vl->values_len);
                return (-1);
@@ -130,10 +138,9 @@ int handle_putval (FILE *fh, char **fields, int fields_num)
        {
                DEBUG ("cmd putval: Wrong number of fields: %i",
                                fields_num);
-               fprintf (fh, "-1 Wrong number of fields: Got %i, "
+               print_to_socket (fh, "-1 Wrong number of fields: Got %i, "
                                "expected at least 3.\n",
                                fields_num);
-               fflush (fh);
                return (-1);
        }
 
@@ -147,8 +154,7 @@ int handle_putval (FILE *fh, char **fields, int fields_num)
        if (status != 0)
        {
                DEBUG ("cmd putval: Cannot parse `%s'", fields[1]);
-               fprintf (fh, "-1 Cannot parse identifier.\n");
-               fflush (fh);
+               print_to_socket (fh, "-1 Cannot parse identifier.\n");
                sfree (identifier_copy);
                return (-1);
        }
@@ -160,8 +166,7 @@ int handle_putval (FILE *fh, char **fields, int fields_num)
                        || ((type_instance != NULL)
                                && (strlen (type_instance) >= sizeof (vl.type_instance))))
        {
-               fprintf (fh, "-1 Identifier too long.\n");
-               fflush (fh);
+               print_to_socket (fh, "-1 Identifier too long.\n");
                sfree (identifier_copy);
                return (-1);
        }
@@ -183,8 +188,7 @@ int handle_putval (FILE *fh, char **fields, int fields_num)
        vl.values = (value_t *) malloc (vl.values_len * sizeof (value_t));
        if (vl.values == NULL)
        {
-               fprintf (fh, "-1 malloc failed.\n");
-               fflush (fh);
+               print_to_socket (fh, "-1 malloc failed.\n");
                sfree (identifier_copy);
                return (-1);
        }
@@ -204,7 +208,7 @@ int handle_putval (FILE *fh, char **fields, int fields_num)
                {
                        if (parse_option (&vl, fields[i]) != 0)
                        {
-                               fprintf (fh, "-1 Error parsing option `%s'\n",
+                               print_to_socket (fh, "-1 Error parsing option `%s'\n",
                                                fields[i]);
                                break;
                        }
@@ -220,8 +224,7 @@ int handle_putval (FILE *fh, char **fields, int fields_num)
        /* Done parsing the options. */
 
        if (i == fields_num)
-               fprintf (fh, "0 Success\n");
-       fflush (fh);
+               print_to_socket (fh, "0 Success\n");
 
        sfree (vl.values); 
        sfree (identifier_copy);
index 609efcb..2ae4532 100644 (file)
@@ -22,8 +22,6 @@
 #ifndef UTILS_CMD_PUTVAL_H
 #define UTILS_CMD_PUTVAL_H 1
 
-#include "plugin.h"
-
 int handle_putval (FILE *fh, char **fields, int fields_num);
 
 #endif /* UTILS_CMD_PUTVAL_H */
index 94d6bda..1d9467f 100644 (file)
@@ -1,6 +1,7 @@
 /**
  * collectd - src/utils_ignorelist.c
  * Copyright (C) 2006 Lubos Stanek <lubek at users.sourceforge.net>
+ * Copyright (C) 2008 Florian Forster <octo at verplant.org>
  *
  * This program is free software; you can redistribute it and/
  * or modify it under the terms of the GNU General Public Li-
@@ -19,6 +20,7 @@
  *
  * Authors:
  *   Lubos Stanek <lubek at users.sourceforge.net>
+ *   Florian Forster <octo at verplant.org>
  **/
 /**
  * ignorelist handles plugin's list of configured collectable
@@ -332,10 +334,8 @@ int ignorelist_match (ignorelist_t *il, const char *entry)
 {
        ignorelist_item_t *traverse;
 
-       assert (il != NULL);
-
        /* if no entries, collect all */
-       if (il->head == NULL)
+       if ((il == NULL) || (il->head == NULL))
                return (0);
 
        if ((entry == NULL) || (strlen (entry) == 0))
diff --git a/src/utils_match.c b/src/utils_match.c
new file mode 100644 (file)
index 0000000..9e75e4e
--- /dev/null
@@ -0,0 +1,288 @@
+/**
+ * collectd - src/utils_match.c
+ * Copyright (C) 2008  Florian octo Forster
+ *
+ * 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 St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#include "utils_match.h"
+
+#include <regex.h>
+
+#define UTILS_MATCH_FLAGS_FREE_USER_DATA 0x01
+
+struct cu_match_s
+{
+  regex_t regex;
+  int flags;
+
+  int (*callback) (const char *str, char * const *matches, size_t matches_num,
+      void *user_data);
+  void *user_data;
+};
+
+/*
+ * Private functions
+ */
+static char *match_substr (const char *str, int begin, int end)
+{
+  char *ret;
+  size_t ret_len;
+
+  if ((begin < 0) || (end < 0) || (begin >= end))
+    return (NULL);
+  if (end > (strlen (str) + 1))
+  {
+    ERROR ("utils_match: match_substr: `end' points after end of string.");
+    return (NULL);
+  }
+
+  ret_len = end - begin;
+  ret = (char *) malloc (sizeof (char) * (ret_len + 1));
+  if (ret == NULL)
+  {
+    ERROR ("utils_match: match_substr: malloc failed.");
+    return (NULL);
+  }
+
+  sstrncpy (ret, str + begin, ret_len + 1);
+  return (ret);
+} /* char *match_substr */
+
+static int default_callback (const char *str,
+    char * const *matches, size_t matches_num, void *user_data)
+{
+  cu_match_value_t *data = (cu_match_value_t *) user_data;
+
+  if (data->ds_type & UTILS_MATCH_DS_TYPE_GAUGE)
+  {
+    gauge_t value;
+    char *endptr = NULL;
+
+    if (matches_num < 2)
+      return (-1);
+
+    value = strtod (matches[1], &endptr);
+    if (matches[1] == endptr)
+      return (-1);
+
+    if ((data->values_num == 0)
+       || (data->ds_type & UTILS_MATCH_CF_GAUGE_LAST))
+    {
+      data->value.gauge = value;
+    }
+    else if (data->ds_type & UTILS_MATCH_CF_GAUGE_AVERAGE)
+    {
+      double f = ((double) data->values_num)
+       / ((double) (data->values_num + 1));
+      data->value.gauge = (data->value.gauge * f) + (value * (1.0 - f));
+    }
+    else if (data->ds_type & UTILS_MATCH_CF_GAUGE_MIN)
+    {
+      if (data->value.gauge > value)
+       data->value.gauge = value;
+    }
+    else if (data->ds_type & UTILS_MATCH_CF_GAUGE_MAX)
+    {
+      if (data->value.gauge < value)
+       data->value.gauge = value;
+    }
+    else
+    {
+      ERROR ("utils_match: default_callback: obj->ds_type is invalid!");
+      return (-1);
+    }
+
+    data->values_num++;
+  }
+  else if (data->ds_type & UTILS_MATCH_DS_TYPE_COUNTER)
+  {
+    counter_t value;
+    char *endptr = NULL;
+
+    if (data->ds_type & UTILS_MATCH_CF_COUNTER_INC)
+    {
+      data->value.counter++;
+      data->values_num++;
+      return (0);
+    }
+
+    if (matches_num < 2)
+      return (-1);
+
+    value = strtoll (matches[1], &endptr, 0);
+    if (matches[1] == endptr)
+      return (-1);
+
+    if (data->ds_type & UTILS_MATCH_CF_COUNTER_SET)
+      data->value.counter = value;
+    else if (data->ds_type & UTILS_MATCH_CF_COUNTER_ADD)
+      data->value.counter += value;
+    else
+    {
+      ERROR ("utils_match: default_callback: obj->ds_type is invalid!");
+      return (-1);
+    }
+
+    data->values_num++;
+  }
+  else
+  {
+    ERROR ("utils_match: default_callback: obj->ds_type is invalid!");
+    return (-1);
+  }
+
+  return (0);
+} /* int default_callback */
+
+/*
+ * Public functions
+ */
+cu_match_t *match_create_callback (const char *regex,
+               int (*callback) (const char *str,
+                 char * const *matches, size_t matches_num, void *user_data),
+               void *user_data)
+{
+  cu_match_t *obj;
+  int status;
+
+  DEBUG ("utils_match: match_create_callback: regex = %s", regex);
+
+  obj = (cu_match_t *) malloc (sizeof (cu_match_t));
+  if (obj == NULL)
+    return (NULL);
+  memset (obj, '\0', sizeof (cu_match_t));
+
+  status = regcomp (&obj->regex, regex, REG_EXTENDED);
+  if (status != 0)
+  {
+    ERROR ("Compiling the regular expression \"%s\" failed.", regex);
+    sfree (obj);
+    return (NULL);
+  }
+
+  obj->callback = callback;
+  obj->user_data = user_data;
+
+  return (obj);
+} /* cu_match_t *match_create_callback */
+
+cu_match_t *match_create_simple (const char *regex, int match_ds_type)
+{
+  cu_match_value_t *user_data;
+  cu_match_t *obj;
+
+  user_data = (cu_match_value_t *) malloc (sizeof (cu_match_value_t));
+  if (user_data == NULL)
+    return (NULL);
+  memset (user_data, '\0', sizeof (cu_match_value_t));
+  user_data->ds_type = match_ds_type;
+
+  obj = match_create_callback (regex, default_callback, user_data);
+  if (obj == NULL)
+  {
+    sfree (user_data);
+    return (NULL);
+  }
+
+  obj->flags |= UTILS_MATCH_FLAGS_FREE_USER_DATA;
+
+  return (obj);
+} /* cu_match_t *match_create_simple */
+
+void match_destroy (cu_match_t *obj)
+{
+  if (obj == NULL)
+    return;
+
+  if (obj->flags & UTILS_MATCH_FLAGS_FREE_USER_DATA)
+  {
+    sfree (obj->user_data);
+  }
+
+  sfree (obj);
+} /* void match_destroy */
+
+int match_apply (cu_match_t *obj, const char *str)
+{
+  int status;
+  regmatch_t re_match[32];
+  char *matches[32];
+  size_t matches_num;
+  int i;
+
+  if ((obj == NULL) || (str == NULL))
+    return (-1);
+
+  status = regexec (&obj->regex, str,
+      STATIC_ARRAY_SIZE (re_match), re_match,
+      /* eflags = */ 0);
+
+  /* Regex did not match */
+  if (status != 0)
+    return (0);
+
+  memset (matches, '\0', sizeof (matches));
+  for (matches_num = 0; matches_num < STATIC_ARRAY_SIZE (matches); matches_num++)
+  {
+    if ((re_match[matches_num].rm_so < 0)
+       || (re_match[matches_num].rm_eo < 0))
+      break;
+
+    matches[matches_num] = match_substr (str,
+       re_match[matches_num].rm_so, re_match[matches_num].rm_eo);
+    if (matches[matches_num] == NULL)
+    {
+      status = -1;
+      break;
+    }
+  }
+
+  if (status != 0)
+  {
+    ERROR ("utils_match: match_apply: match_substr failed.");
+  }
+  else
+  {
+    status = obj->callback (str, matches, matches_num, obj->user_data);
+    if (status != 0)
+    {
+      ERROR ("utils_match: match_apply: callback failed.");
+    }
+  }
+
+  for (i = 0; i < matches_num; i++)
+  {
+    sfree (matches[i]);
+  }
+
+  return (status);
+} /* int match_apply */
+
+void *match_get_user_data (cu_match_t *obj)
+{
+  if (obj == NULL)
+    return (NULL);
+  return (obj->user_data);
+} /* void *match_get_user_data */
+
+/* vim: set sw=2 sts=2 ts=8 : */
diff --git a/src/utils_match.h b/src/utils_match.h
new file mode 100644 (file)
index 0000000..a39c869
--- /dev/null
@@ -0,0 +1,142 @@
+/**
+ * collectd - src/utils_match.h
+ * Copyright (C) 2008  Florian octo Forster
+ *
+ * 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 St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#ifndef UTILS_MATCH_H
+#define UTILS_MATCH_H 1
+
+#include "plugin.h"
+
+/*
+ * Defines
+ */
+#define UTILS_MATCH_DS_TYPE_GAUGE   0x10
+#define UTILS_MATCH_DS_TYPE_COUNTER 0x20
+
+#define UTILS_MATCH_CF_GAUGE_AVERAGE 0x01
+#define UTILS_MATCH_CF_GAUGE_MIN     0x02
+#define UTILS_MATCH_CF_GAUGE_MAX     0x04
+#define UTILS_MATCH_CF_GAUGE_LAST    0x08
+
+#define UTILS_MATCH_CF_COUNTER_SET   0x01
+#define UTILS_MATCH_CF_COUNTER_ADD   0x02
+#define UTILS_MATCH_CF_COUNTER_INC   0x04
+
+/*
+ * Data types
+ */
+struct cu_match_s;
+typedef struct cu_match_s cu_match_t;
+
+struct cu_match_value_s
+{
+  int ds_type;
+  value_t value;
+  unsigned int values_num;
+};
+typedef struct cu_match_value_s cu_match_value_t;
+
+/*
+ * Prototypes
+ */
+/*
+ * NAME
+ *  match_create_callback
+ *
+ * DESCRIPTION
+ *  Creates a new `cu_match_t' object which will use the regular expression
+ *  `regex' to match lines, see the `match_apply' method below. If the line
+ *  matches, the callback passed in `callback' will be called along with the
+ *  pointer `user_pointer'.
+ *  The string that's passed to the callback depends on the regular expression:
+ *  If the regular expression includes a sub-match, i. e. something like
+ *    "value=([0-9][0-9]*)"
+ *  then only the submatch (the part in the parenthesis) will be passed to the
+ *  callback. If there is no submatch, then the entire string is passed to the
+ *  callback.
+ */
+cu_match_t *match_create_callback (const char *regex,
+               int (*callback) (const char *str,
+                 char * const *matches, size_t matches_num, void *user_data),
+               void *user_data);
+
+/*
+ * NAME
+ *  match_create_simple
+ *
+ * DESCRIPTION
+ *  Creates a new `cu_match_t' with a default callback. The user data for that
+ *  default callback will be a `cu_match_value_t' structure, with
+ *  `ds_type' copied to the structure. The default callback will handle the
+ *  string as containing a number (see strtoll(3) and strtod(3)) and store that
+ *  number in the `value' member. How that is done depends on `ds_type':
+ *
+ *  UTILS_MATCH_DS_TYPE_GAUGE
+ *    The function will search for a floating point number in the string and
+ *    store it in value.gauge.
+ *  UTILS_MATCH_DS_TYPE_COUNTER_SET
+ *    The function will search for an integer in the string and store it in
+ *    value.counter.
+ *  UTILS_MATCH_DS_TYPE_COUNTER_ADD
+ *    The function will search for an integer in the string and add it to the
+ *    value in value.counter.
+ *  UTILS_MATCH_DS_TYPE_COUNTER_INC
+ *    The function will not search for anything in the string and increase
+ *    value.counter by one.
+ */
+cu_match_t *match_create_simple (const char *regex, int ds_type);
+
+/*
+ * NAME
+ *  match_destroy
+ *
+ * DESCRIPTION
+ *  Destroys the object and frees all internal resources.
+ */
+void match_destroy (cu_match_t *obj);
+
+/*
+ * NAME
+ *  match_apply
+ *
+ * DESCRIPTION
+ *  Tries to match the string `str' with the regular expression of `obj'. If
+ *  the string matches, calls the callback in `obj' with the (sub-)match.
+ *
+ *  The user_data pointer passed to `match_create_callback' is NOT freed
+ *  automatically. The `cu_match_value_t' structure allocated by
+ *  `match_create_callback' is freed automatically.
+ */
+int match_apply (cu_match_t *obj, const char *str);
+
+/*
+ * NAME
+ *  match_get_user_data
+ *
+ * DESCRIPTION
+ *  Returns the pointer passed to `match_create_callback' or a pointer to the
+ *  `cu_match_value_t' structure allocated by `match_create_callback'.
+ */
+void *match_get_user_data (cu_match_t *obj);
+
+#endif /* UTILS_MATCH_H */
+
+/* vim: set sw=2 sts=2 ts=8 : */
diff --git a/src/utils_tail.c b/src/utils_tail.c
new file mode 100644 (file)
index 0000000..eaf8f73
--- /dev/null
@@ -0,0 +1,244 @@
+/**
+ * collectd - src/utils_tail.c
+ * Copyright (C) 2007-2008  C-Ware, Inc.
+ * Copyright (C) 2008  Florian Forster
+ *
+ * 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; only version 2 of the License is applicable.
+ *
+ * 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 St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Author:
+ *   Luke Heberling <lukeh at c-ware.com>
+ *   Florian Forster <octo at verplant.org>
+ *
+ * Description:
+ *   Encapsulates useful code for plugins which must watch for appends to
+ *   the end of a file.
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "utils_tail.h"
+
+struct cu_tail_s
+{
+       char  *file;
+       FILE  *fh;
+       struct stat stat;
+};
+
+static int cu_tail_reopen (cu_tail_t *obj)
+{
+  int seek_end = 0;
+  FILE *fh;
+  struct stat stat_buf;
+  int status;
+
+  memset (&stat_buf, 0, sizeof (stat_buf));
+  status = stat (obj->file, &stat_buf);
+  if (status != 0)
+  {
+    char errbuf[1024];
+    ERROR ("utils_tail: stat (%s) failed: %s", obj->file,
+       sstrerror (errno, errbuf, sizeof (errbuf)));
+    return (-1);
+  }
+
+  /* The file is already open.. */
+  if ((obj->fh != NULL) && (stat_buf.st_ino == obj->stat.st_ino))
+  {
+    /* Seek to the beginning if file was truncated */
+    if (stat_buf.st_size < obj->stat.st_size)
+    {
+      INFO ("utils_tail: File `%s' was truncated.", obj->file);
+      status = fseek (obj->fh, 0, SEEK_SET);
+      if (status != 0)
+      {
+       char errbuf[1024];
+       ERROR ("utils_tail: fseek (%s) failed: %s", obj->file,
+           sstrerror (errno, errbuf, sizeof (errbuf)));
+       fclose (obj->fh);
+       obj->fh = NULL;
+       return (-1);
+      }
+    }
+    memcpy (&obj->stat, &stat_buf, sizeof (struct stat));
+    return (1);
+  }
+
+  /* Seek to the end if we re-open the same file again or the file opened
+   * is the first at all or the first after an error */
+  if ((obj->stat.st_ino == 0) || (obj->stat.st_ino == stat_buf.st_ino))
+    seek_end = 1;
+
+  fh = fopen (obj->file, "r");
+  if (fh == NULL)
+  {
+    char errbuf[1024];
+    ERROR ("utils_tail: fopen (%s) failed: %s", obj->file,
+       sstrerror (errno, errbuf, sizeof (errbuf)));
+    return (-1);
+  }
+
+  if (seek_end != 0)
+  {
+    status = fseek (fh, 0, SEEK_END);
+    if (status != 0)
+    {
+      char errbuf[1024];
+      ERROR ("utils_tail: fseek (%s) failed: %s", obj->file,
+         sstrerror (errno, errbuf, sizeof (errbuf)));
+      fclose (fh);
+      return (-1);
+    }
+  }
+
+  if (obj->fh != NULL)
+    fclose (obj->fh);
+  obj->fh = fh;
+  memcpy (&obj->stat, &stat_buf, sizeof (struct stat));
+
+  return (0);
+} /* int cu_tail_reopen */
+
+cu_tail_t *cu_tail_create (const char *file)
+{
+       cu_tail_t *obj;
+
+       obj = (cu_tail_t *) malloc (sizeof (cu_tail_t));
+       if (obj == NULL)
+               return (NULL);
+       memset (obj, '\0', sizeof (cu_tail_t));
+
+       obj->file = strdup (file);
+       if (obj->file == NULL)
+       {
+               free (obj);
+               return (NULL);
+       }
+
+       obj->fh = NULL;
+
+       return (obj);
+} /* cu_tail_t *cu_tail_create */
+
+int cu_tail_destroy (cu_tail_t *obj)
+{
+       if (obj->fh != NULL)
+               fclose (obj->fh);
+       free (obj->file);
+       free (obj);
+
+       return (0);
+} /* int cu_tail_destroy */
+
+int cu_tail_readline (cu_tail_t *obj, char *buf, int buflen)
+{
+  int status;
+
+  if (buflen < 1)
+  {
+    ERROR ("utils_tail: cu_tail_readline: buflen too small: %i bytes.",
+       buflen);
+    return (-1);
+  }
+
+  if (obj->fh == NULL)
+  {
+    status = cu_tail_reopen (obj);
+    if (status < 0)
+      return (status);
+  }
+  assert (obj->fh != NULL);
+
+  /* Try to read from the filehandle. If that succeeds, everything appears to
+   * be fine and we can return. */
+  if (fgets (buf, buflen, obj->fh) != NULL)
+  {
+    buf[buflen - 1] = 0;
+    return (0);
+  }
+
+  /* Check if we encountered an error */
+  if (ferror (obj->fh) != 0)
+  {
+    /* Jupp, error. Force `cu_tail_reopen' to reopen the file.. */
+    fclose (obj->fh);
+    obj->fh = NULL;
+  }
+  /* else: eof -> check if the file was moved away and reopen the new file if
+   * so.. */
+
+  status = cu_tail_reopen (obj);
+  /* error -> return with error */
+  if (status < 0)
+    return (status);
+  /* file end reached and file not reopened -> nothing more to read */
+  else if (status > 0)
+  {
+    buf[0] = 0;
+    return (0);
+  }
+
+  /* If we get here: file was re-opened and there may be more to read.. Let's
+   * try again. */
+  if (fgets (buf, buflen, obj->fh) != NULL)
+  {
+    buf[buflen - 1] = 0;
+    return (0);
+  }
+
+  if (ferror (obj->fh) != 0)
+  {
+    char errbuf[1024];
+    WARNING ("utils_tail: fgets (%s) returned an error: %s", obj->file,
+       sstrerror (errno, errbuf, sizeof (errbuf)));
+    fclose (obj->fh);
+    obj->fh = NULL;
+    return (-1);
+  }
+
+  /* EOf, well, apparently the new file is empty.. */
+  buf[0] = 0;
+  return (0);
+} /* int cu_tail_readline */
+
+int cu_tail_read (cu_tail_t *obj, char *buf, int buflen, tailfunc_t *callback,
+               void *data)
+{
+       int status;
+
+       while (42)
+       {
+               status = cu_tail_readline (obj, buf, buflen);
+               if (status != 0)
+               {
+                       ERROR ("utils_tail: cu_tail_read: cu_tail_readline "
+                                       "failed.");
+                       break;
+               }
+
+               /* check for EOF */
+               if (buf[0] == 0)
+                       break;
+
+               status = callback (data, buf, buflen);
+               if (status != 0)
+               {
+                       ERROR ("utils_tail: cu_tail_read: callback returned "
+                                       "status %i.", status);
+                       break;
+               }
+       }
+
+       return status;
+} /* int cu_tail_read */
diff --git a/src/utils_tail.h b/src/utils_tail.h
new file mode 100644 (file)
index 0000000..c479319
--- /dev/null
@@ -0,0 +1,83 @@
+/**
+ * collectd - src/utils_tail.h
+ * Copyright (C) 2007-2008  C-Ware, Inc.
+ *
+ * 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; only version 2 of the License is applicable.
+ *
+ * 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 St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Author:
+ *   Luke Heberling <lukeh at c-ware.com>
+ *
+ * DESCRIPTION
+ *   Facilitates reading information that is appended to a file, taking into
+ *   account that the file may be rotated and a new file created under the
+ *   same name.
+ **/
+
+#ifndef UTILS_TAIL_H
+#define UTILS_TAIL_H 1
+
+struct cu_tail_s;
+typedef struct cu_tail_s cu_tail_t;
+
+typedef int tailfunc_t(void *data, char *buf, int buflen);
+
+/*
+ * NAME
+ *   cu_tail_create
+ *
+ * DESCRIPTION
+ *   Allocates a new tail object..
+ *
+ * PARAMETERS
+ *   `file'       The name of the file to be tailed.
+ */
+cu_tail_t *cu_tail_create (const char *file);
+
+/*
+ * cu_tail_destroy
+ *
+ * Takes a tail object returned by `cu_tail_create' and destroys it, freeing
+ * all internal memory.
+ *
+ * Returns 0 when successful and non-zero otherwise.
+ */
+int cu_tail_destroy (cu_tail_t *obj);
+
+/*
+ * cu_tail_readline
+ *
+ * Reads from the file until `buflen' characters are read, a newline
+ * character is read, or an eof condition is encountered. `buf' is
+ * always null-terminated on successful return and isn't touched when non-zero
+ * is returned.
+ *
+ * You can check if the EOF condition is reached by looking at the buffer: If
+ * the length of the string stored in the buffer is zero, EOF occurred.
+ * Otherwise at least the newline character will be in the buffer.
+ *
+ * Returns 0 when successful and non-zero otherwise.
+ */
+int cu_tail_readline (cu_tail_t *obj, char *buf, int buflen);
+
+/*
+ * cu_tail_readline
+ *
+ * Reads from the file until eof condition or an error is encountered.
+ *
+ * Returns 0 when successful and non-zero otherwise.
+ */
+int cu_tail_read (cu_tail_t *obj, char *buf, int buflen, tailfunc_t *callback,
+               void *data);
+
+#endif /* UTILS_TAIL_H */
diff --git a/src/utils_tail_match.c b/src/utils_tail_match.c
new file mode 100644 (file)
index 0000000..34fe2dc
--- /dev/null
@@ -0,0 +1,262 @@
+/*
+ * collectd - src/utils_tail_match.c
+ * Copyright (C) 2007-2008  C-Ware, Inc.
+ * Copyright (C) 2008       Florian Forster
+ *
+ * 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; only version 2 of the License is applicable.
+ *
+ * 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 St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Author:
+ *   Luke Heberling <lukeh at c-ware.com>
+ *   Florian Forster <octo at verplant.org>
+ *
+ * Description:
+ *   Encapsulates useful code to plugins which must parse a log file.
+ */
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "utils_match.h"
+#include "utils_tail.h"
+#include "utils_tail_match.h"
+
+struct cu_tail_match_simple_s
+{
+  char plugin[DATA_MAX_NAME_LEN];
+  char plugin_instance[DATA_MAX_NAME_LEN];
+  char type[DATA_MAX_NAME_LEN];
+  char type_instance[DATA_MAX_NAME_LEN];
+};
+typedef struct cu_tail_match_simple_s cu_tail_match_simple_t;
+
+struct cu_tail_match_match_s
+{
+  cu_match_t *match;
+  void *user_data;
+  int (*submit) (cu_match_t *match, void *user_data);
+  void (*free) (void *user_data);
+};
+typedef struct cu_tail_match_match_s cu_tail_match_match_t;
+
+struct cu_tail_match_s
+{
+  int flags;
+  cu_tail_t *tail;
+
+  cu_tail_match_match_t *matches;
+  size_t matches_num;
+};
+
+/*
+ * Private functions
+ */
+static int simple_submit_match (cu_match_t *match, void *user_data)
+{
+  cu_tail_match_simple_t *data = (cu_tail_match_simple_t *) user_data;
+  cu_match_value_t *match_value;
+  value_list_t vl = VALUE_LIST_INIT;
+  value_t values[1];
+
+  match_value = (cu_match_value_t *) match_get_user_data (match);
+  if (match_value == NULL)
+    return (-1);
+
+  if ((match_value->ds_type & UTILS_MATCH_DS_TYPE_GAUGE)
+      && (match_value->values_num == 0))
+    values[0].gauge = NAN;
+  else
+    values[0] = match_value->value;
+
+  vl.values = values;
+  vl.values_len = 1;
+  vl.time = time (NULL);
+  sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+  sstrncpy (vl.plugin, data->plugin, sizeof (vl.plugin));
+  sstrncpy (vl.plugin_instance, data->plugin_instance,
+      sizeof (vl.plugin_instance));
+  sstrncpy (vl.type_instance, data->type_instance,
+      sizeof (vl.type_instance));
+
+  plugin_dispatch_values (data->type, &vl);
+
+  if (match_value->ds_type & UTILS_MATCH_DS_TYPE_GAUGE)
+  {
+    match_value->value.gauge = NAN;
+    match_value->values_num = 0;
+  }
+
+  return (0);
+} /* int simple_submit_match */
+
+static int tail_callback (void *data, char *buf, int buflen)
+{
+  cu_tail_match_t *obj = (cu_tail_match_t *) data;
+  int i;
+
+  for (i = 0; i < obj->matches_num; i++)
+    match_apply (obj->matches[i].match, buf);
+
+  return (0);
+} /* int tail_callback */
+
+/*
+ * Public functions
+ */
+cu_tail_match_t *tail_match_create (const char *filename)
+{
+  cu_tail_match_t *obj;
+
+  obj = (cu_tail_match_t *) malloc (sizeof (cu_tail_match_t));
+  if (obj == NULL)
+    return (NULL);
+  memset (obj, '\0', sizeof (cu_tail_match_t));
+
+  obj->tail = cu_tail_create (filename);
+  if (obj->tail == NULL)
+  {
+    sfree (obj);
+    return (NULL);
+  }
+
+  return (obj);
+} /* cu_tail_match_t *tail_match_create */
+
+void tail_match_destroy (cu_tail_match_t *obj)
+{
+  int i;
+
+  if (obj == NULL)
+    return;
+
+  if (obj->tail != NULL)
+  {
+    cu_tail_destroy (obj->tail);
+    obj->tail = NULL;
+  }
+
+  for (i = 0; i < obj->matches_num; i++)
+  {
+    cu_tail_match_match_t *match = obj->matches + i;
+    if (match->match != NULL)
+    {
+      match_destroy (match->match);
+      match->match = NULL;
+    }
+
+    if ((match->user_data != NULL)
+       && (match->free != NULL))
+      (*match->free) (match->user_data);
+    match->user_data = NULL;
+  }
+
+  sfree (obj->matches);
+  sfree (obj);
+} /* void tail_match_destroy */
+
+int tail_match_add_match (cu_tail_match_t *obj, cu_match_t *match,
+    int (*submit_match) (cu_match_t *match, void *user_data),
+    void *user_data,
+    void (*free_user_data) (void *user_data))
+{
+  cu_tail_match_match_t *temp;
+
+  temp = (cu_tail_match_match_t *) realloc (obj->matches,
+      sizeof (cu_tail_match_match_t) * (obj->matches_num + 1));
+  if (temp == NULL)
+    return (-1);
+
+  obj->matches = temp;
+  obj->matches_num++;
+
+  temp = obj->matches + (obj->matches_num - 1);
+
+  temp->match = match;
+  temp->user_data = user_data;
+  temp->submit = submit_match;
+  temp->free = free_user_data;
+
+  return (0);
+} /* int tail_match_add_match */
+
+int tail_match_add_match_simple (cu_tail_match_t *obj,
+    const char *regex, int ds_type,
+    const char *plugin, const char *plugin_instance,
+    const char *type, const char *type_instance)
+{
+  cu_match_t *match;
+  cu_tail_match_simple_t *user_data;
+  int status;
+
+  match = match_create_simple (regex, ds_type);
+  if (match == NULL)
+    return (-1);
+
+  user_data = (cu_tail_match_simple_t *) malloc (sizeof (cu_tail_match_simple_t));
+  if (user_data == NULL)
+  {
+    match_destroy (match);
+    return (-1);
+  }
+  memset (user_data, '\0', sizeof (cu_tail_match_simple_t));
+
+  sstrncpy (user_data->plugin, plugin, sizeof (user_data->plugin));
+  if (plugin_instance != NULL)
+    sstrncpy (user_data->plugin_instance, plugin_instance,
+       sizeof (user_data->plugin_instance));
+
+  sstrncpy (user_data->type, type, sizeof (user_data->type));
+  if (type_instance != NULL)
+    sstrncpy (user_data->type_instance, type_instance,
+       sizeof (user_data->type_instance));
+
+  status = tail_match_add_match (obj, match, simple_submit_match,
+      user_data, free);
+
+  if (status != 0)
+  {
+    match_destroy (match);
+    sfree (user_data);
+  }
+
+  return (status);
+} /* int tail_match_add_match_simple */
+
+int tail_match_read (cu_tail_match_t *obj)
+{
+  char buffer[4096];
+  int status;
+  int i;
+
+  status = cu_tail_read (obj->tail, buffer, sizeof (buffer), tail_callback,
+      (void *) obj);
+  if (status != 0)
+  {
+    ERROR ("tail_match: cu_tail_read failed.");
+    return (status);
+  }
+
+  for (i = 0; i < obj->matches_num; i++)
+  {
+    cu_tail_match_match_t *lt_match = obj->matches + i;
+
+    if (lt_match->submit == NULL)
+      continue;
+
+    (*lt_match->submit) (lt_match->match, lt_match->user_data);
+  }
+
+  return (0);
+} /* int tail_match_read */
+
+/* vim: set sw=2 sts=2 ts=8 : */
diff --git a/src/utils_tail_match.h b/src/utils_tail_match.h
new file mode 100644 (file)
index 0000000..d08c728
--- /dev/null
@@ -0,0 +1,126 @@
+/*
+ * collectd - src/utils_tail_match.h
+ * Copyright (C) 2007-2008  C-Ware, Inc.
+ * Copyright (C) 2008       Florian Forster
+ *
+ * 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; only version 2 of the License is applicable.
+ *
+ * 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 St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Luke Heberling <lukeh at c-ware.com>
+ *   Florian Forster <octo at verplant.org>
+ *
+ * Description:
+ *   `tail_match' uses `utils_tail' and `utils_match' to tail a file and try to
+ *   match it using several regular expressions. Matches are then passed to
+ *   user-provided callback functions or default handlers. This should keep all
+ *   of the parsing logic out of the actual plugin, which only operate with
+ *   regular expressions.
+ */
+
+#include "utils_match.h"
+
+struct cu_tail_match_s;
+typedef struct cu_tail_match_s cu_tail_match_t;
+
+/*
+ * NAME
+ *   tail_match_create
+ *
+ * DESCRIPTION
+ *   Allocates, initializes and returns a new `cu_tail_match_t' object.
+ *
+ * PARAMETERS
+ *   `filename'  The name to read data from.
+ *
+ * RETURN VALUE
+ *   Returns NULL upon failure, non-NULL otherwise.
+ */
+cu_tail_match_t *tail_match_create (const char *filename);
+
+/*
+ * NAME
+ *   tail_match_destroy
+ *
+ * DESCRIPTION
+ *   Releases resources used by the `cu_tail_match_t' object.
+ *
+ * PARAMETERS
+ *   The object to destroy.
+ */
+void tail_match_destroy (cu_tail_match_t *obj);
+
+/*
+ * NAME
+ *   tail_match_add_match
+ *
+ * DESCRIPTION
+ *   Adds a match, in form of a `cu_match_t' object, to the object.
+ *   After data has been read from the logfile (using utils_tail) the callback
+ *   function `submit_match' is called with the match object and the user
+ *   supplied data.
+ *   Please note that his function is called regardless whether this match
+ *   matched any lines recently or not.
+ *   When `tail_match_destroy' is called the `user_data' pointer is freed using
+ *   the `free_user_data' callback - if it is not NULL.
+ *   When using this interface the `tail_match' module doesn't dispatch any values
+ *   itself - all that has to happen in either the match-callbacks or the
+ *   submit_match callback.
+ *
+ * RETURN VALUE
+ *   Zero upon success, non-zero otherwise.
+ */
+int tail_match_add_match (cu_tail_match_t *obj, cu_match_t *match,
+    int (*submit_match) (cu_match_t *match, void *user_data),
+    void *user_data,
+    void (*free_user_data) (void *user_data));
+
+/*
+ * NAME
+ *  tail_match_add_match_simple
+ *
+ * DESCRIPTION
+ *  A simplified version of `tail_match_add_match'. The regular expressen `regex'
+ *  must match a number, which is then dispatched according to `ds_type'. See
+ *  the `match_create_simple' function in utils_match.h for a description how
+ *  this flag effects calculation of a new value.
+ *  The values gathered are dispatched by the tail_match module in this case. The
+ *  passed `plugin', `plugin_instance', `type', and `type_instance' are
+ *  directly used when submitting these values.
+ *
+ * RETURN VALUE
+ *   Zero upon success, non-zero otherwise.
+ */
+int tail_match_add_match_simple (cu_tail_match_t *obj,
+    const char *regex, int ds_type,
+    const char *plugin, const char *plugin_instance,
+    const char *type, const char *type_instance);
+
+/*
+ * NAME
+ *   tail_match_read
+ *
+ * DESCRIPTION
+ *   This function should be called periodically by plugins. It reads new lines
+ *   from the logfile using `utils_tail' and tries to match them using all
+ *   added `utils_match' objects.
+ *   After all lines have been read and processed, the submit_match callback is
+ *   called or, in case of tail_match_add_match_simple, the data is dispatched to
+ *   the daemon directly.
+ *
+ * RETURN VALUE
+ *   Zero on success, nonzero on failure.
+*/
+int tail_match_read (cu_tail_match_t *obj);
+
+/* vim: set sw=2 sts=2 ts=8 : */
index 7676a5a..ddce422 100644 (file)
@@ -41,11 +41,13 @@ typedef struct threshold_s
   char plugin_instance[DATA_MAX_NAME_LEN];
   char type[DATA_MAX_NAME_LEN];
   char type_instance[DATA_MAX_NAME_LEN];
+  char data_source[DATA_MAX_NAME_LEN];
   gauge_t warning_min;
   gauge_t warning_max;
   gauge_t failure_min;
   gauge_t failure_max;
   int flags;
+  struct threshold_s *next;
 } threshold_t;
 /* }}} */
 
@@ -62,11 +64,31 @@ static pthread_mutex_t threshold_lock = PTHREAD_MUTEX_INITIALIZER;
  * The following functions add, delete, search, etc. configured thresholds to
  * the underlying AVL trees.
  * {{{ */
+static threshold_t *threshold_get (const char *hostname,
+    const char *plugin, const char *plugin_instance,
+    const char *type, const char *type_instance)
+{
+  char name[6 * DATA_MAX_NAME_LEN];
+  threshold_t *th = NULL;
+
+  format_name (name, sizeof (name),
+      (hostname == NULL) ? "" : hostname,
+      (plugin == NULL) ? "" : plugin, plugin_instance,
+      (type == NULL) ? "" : type, type_instance);
+  name[sizeof (name) - 1] = '\0';
+
+  if (c_avl_get (threshold_tree, name, (void *) &th) == 0)
+    return (th);
+  else
+    return (NULL);
+} /* threshold_t *threshold_get */
+
 static int ut_threshold_add (const threshold_t *th)
 {
   char name[6 * DATA_MAX_NAME_LEN];
   char *name_copy;
   threshold_t *th_copy;
+  threshold_t *th_ptr;
   int status = 0;
 
   if (format_name (name, sizeof (name), th->host,
@@ -92,11 +114,29 @@ static int ut_threshold_add (const threshold_t *th)
     return (-1);
   }
   memcpy (th_copy, th, sizeof (threshold_t));
+  th_ptr = NULL;
 
   DEBUG ("ut_threshold_add: Adding entry `%s'", name);
 
   pthread_mutex_lock (&threshold_lock);
-  status = c_avl_insert (threshold_tree, name_copy, th_copy);
+
+  th_ptr = threshold_get (th->host, th->plugin, th->plugin_instance,
+      th->type, th->type_instance);
+
+  while ((th_ptr != NULL) && (th_ptr->next != NULL))
+    th_ptr = th_ptr->next;
+
+  if (th_ptr == NULL) /* no such threshold yet */
+  {
+    status = c_avl_insert (threshold_tree, name_copy, th_copy);
+  }
+  else /* th_ptr points to the last threshold in the list */
+  {
+    th_ptr->next = th_copy;
+    /* name_copy isn't needed */
+    sfree (name_copy);
+  }
+
   pthread_mutex_unlock (&threshold_lock);
 
   if (status != 0)
@@ -118,6 +158,22 @@ static int ut_threshold_add (const threshold_t *th)
  * The following approximately two hundred functions are used to handle the
  * configuration and fill the threshold list.
  * {{{ */
+static int ut_config_type_datasource (threshold_t *th, oconfig_item_t *ci)
+{
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("threshold values: The `DataSource' option needs exactly one "
+       "string argument.");
+    return (-1);
+  }
+
+  sstrncpy (th->data_source, ci->values[0].value.string,
+      sizeof (th->data_source));
+
+  return (0);
+} /* int ut_config_type_datasource */
+
 static int ut_config_type_instance (threshold_t *th, oconfig_item_t *ci)
 {
   if ((ci->values_num != 1)
@@ -243,6 +299,8 @@ static int ut_config_type (const threshold_t *th_orig, oconfig_item_t *ci)
 
     if (strcasecmp ("Instance", option->key) == 0)
       status = ut_config_type_instance (&th, option);
+    else if (strcasecmp ("DataSource", option->key) == 0)
+      status = ut_config_type_datasource (&th, option);
     else if ((strcasecmp ("WarningMax", option->key) == 0)
        || (strcasecmp ("FailureMax", option->key) == 0))
       status = ut_config_type_max (&th, option);
@@ -443,25 +501,6 @@ int ut_config (const oconfig_item_t *ci)
  */
 /* }}} */
 
-static threshold_t *threshold_get (const char *hostname,
-    const char *plugin, const char *plugin_instance,
-    const char *type, const char *type_instance)
-{
-  char name[6 * DATA_MAX_NAME_LEN];
-  threshold_t *th = NULL;
-
-  format_name (name, sizeof (name),
-      (hostname == NULL) ? "" : hostname,
-      (plugin == NULL) ? "" : plugin, plugin_instance,
-      (type == NULL) ? "" : type, type_instance);
-  name[sizeof (name) - 1] = '\0';
-
-  if (c_avl_get (threshold_tree, name, (void *) &th) == 0)
-    return (th);
-  else
-    return (NULL);
-} /* threshold_t *threshold_get */
-
 static threshold_t *threshold_search (const data_set_t *ds,
     const value_list_t *vl)
 {
@@ -507,128 +546,84 @@ static threshold_t *threshold_search (const data_set_t *ds,
   return (NULL);
 } /* threshold_t *threshold_search */
 
-int ut_check_threshold (const data_set_t *ds, const value_list_t *vl)
-{
+/*
+ * int ut_report_state
+ *
+ * Checks if the `state' differs from the old state and creates a notification
+ * if appropriate.
+ * Does not fail.
+ */
+static int ut_report_state (const data_set_t *ds,
+    const value_list_t *vl,
+    const threshold_t *th,
+    const gauge_t *values,
+    int ds_index,
+    int state)
+{ /* {{{ */
+  int state_old;
   notification_t n;
-  threshold_t *th;
-  gauge_t *values;
-  int i;
-
-  int state_orig;
-  int state_new = STATE_OKAY;
-  int ds_index = 0;
 
   char *buf;
   size_t bufsize;
-  int status;
-
-  if (threshold_tree == NULL)
-    return (0);
 
-  /* Is this lock really necessary? So far, thresholds are only inserted at
-   * startup. -octo */
-  pthread_mutex_lock (&threshold_lock);
-  th = threshold_search (ds, vl);
-  pthread_mutex_unlock (&threshold_lock);
-  if (th == NULL)
-    return (0);
-
-  DEBUG ("ut_check_threshold: Found matching threshold");
-
-  values = uc_get_rate (ds, vl);
-  if (values == NULL)
-    return (0);
+  int status;
 
-  state_orig = uc_get_state (ds, vl);
+  state_old = uc_get_state (ds, vl);
 
-  for (i = 0; i < ds->ds_num; i++)
+  /* If the state didn't change, only report if `persistent' is specified and
+   * the state is not `okay'. */
+  if (state == state_old)
   {
-    int is_inverted = 0;
-    int is_warning = 0;
-    int is_failure = 0;
-
-    if ((th->flags & UT_FLAG_INVERT) != 0)
-    {
-      is_inverted = 1;
-      is_warning--;
-      is_failure--;
-    }
-    if ((!isnan (th->failure_min) && (th->failure_min > values[i]))
-       || (!isnan (th->failure_max) && (th->failure_max < values[i])))
-      is_failure++;
-    if ((!isnan (th->warning_min) && (th->warning_min > values[i]))
-       || (!isnan (th->warning_max) && (th->warning_max < values[i])))
-      is_warning++;
-
-    if ((is_failure != 0) && (state_new != STATE_ERROR))
-    {
-      state_new = STATE_ERROR;
-      ds_index = i;
-    }
-    else if ((is_warning != 0)
-       && (state_new != STATE_ERROR)
-       && (state_new != STATE_WARNING))
-    {
-      state_new = STATE_WARNING;
-      ds_index = i;
-    }
+    if ((th->flags & UT_FLAG_PERSIST) == 0)
+      return (0);
+    else if (state == STATE_OKAY)
+      return (0);
   }
 
-  if (state_new != state_orig)
-    uc_set_state (ds, vl, state_new);
-
-  /* Return here if we're not going to send a notification */
-  if ((state_new == state_orig)
-      && ((state_new == STATE_OKAY)
-       || ((th->flags & UT_FLAG_PERSIST) == 0)))
-  {
-    sfree (values);
-    return (0);
-  }
+  if (state != state_old)
+    uc_set_state (ds, vl, state);
 
   NOTIFICATION_INIT_VL (&n, vl, ds);
-  {
-    /* Copy the associative members */
-    if (state_new == STATE_OKAY)
-      n.severity = NOTIF_OKAY;
-    else if (state_new == STATE_WARNING)
-      n.severity = NOTIF_WARNING;
-    else
-      n.severity = NOTIF_FAILURE;
 
-    n.time = vl->time;
+  buf = n.message;
+  bufsize = sizeof (n.message);
+
+  if (state == STATE_OKAY)
+    n.severity = NOTIF_OKAY;
+  else if (state == STATE_WARNING)
+    n.severity = NOTIF_WARNING;
+  else
+    n.severity = NOTIF_FAILURE;
+
+  n.time = vl->time;
 
-    buf = n.message;
-    bufsize = sizeof (n.message);
+  status = snprintf (buf, bufsize, "Host %s, plugin %s",
+      vl->host, vl->plugin);
+  buf += status;
+  bufsize -= status;
 
-    status = snprintf (buf, bufsize, "Host %s, plugin %s",
-       vl->host, vl->plugin);
+  if (vl->plugin_instance[0] != '\0')
+  {
+    status = snprintf (buf, bufsize, " (instance %s)",
+       vl->plugin_instance);
     buf += status;
     bufsize -= status;
+  }
 
-    if (vl->plugin_instance[0] != '\0')
-    {
-      status = snprintf (buf, bufsize, " (instance %s)",
-         vl->plugin_instance);
-      buf += status;
-      bufsize -= status;
-    }
+  status = snprintf (buf, bufsize, " type %s", ds->type);
+  buf += status;
+  bufsize -= status;
 
-    status = snprintf (buf, bufsize, " type %s", ds->type);
+  if (vl->type_instance[0] != '\0')
+  {
+    status = snprintf (buf, bufsize, " (instance %s)",
+       vl->type_instance);
     buf += status;
     bufsize -= status;
-
-    if (vl->type_instance[0] != '\0')
-    {
-      status = snprintf (buf, bufsize, " (instance %s)",
-         vl->type_instance);
-      buf += status;
-      bufsize -= status;
-    }
   }
 
-  /* Send a okay notification */
-  if (state_new == STATE_OKAY)
+  /* Send an okay notification */
+  if (state == STATE_OKAY)
   {
     status = snprintf (buf, bufsize, ": All data sources are within range again.");
     buf += status;
@@ -639,8 +634,8 @@ int ut_check_threshold (const data_set_t *ds, const value_list_t *vl)
     double min;
     double max;
 
-    min = (state_new == STATE_ERROR) ? th->failure_min : th->warning_min;
-    max = (state_new == STATE_ERROR) ? th->failure_max : th->warning_max;
+    min = (state == STATE_ERROR) ? th->failure_min : th->warning_min;
+    max = (state == STATE_ERROR) ? th->failure_max : th->warning_max;
 
     if (th->flags & UT_FLAG_INVERT)
     {
@@ -649,8 +644,8 @@ int ut_check_threshold (const data_set_t *ds, const value_list_t *vl)
        status = snprintf (buf, bufsize, ": Data source \"%s\" is currently "
            "%f. That is within the %s region of %f and %f.",
            ds->ds[ds_index].name, values[ds_index],
-           (state_new == STATE_ERROR) ? "failure" : "warning",
-           min, min);
+           (state == STATE_ERROR) ? "failure" : "warning",
+           min, max);
       }
       else
       {
@@ -658,7 +653,7 @@ int ut_check_threshold (const data_set_t *ds, const value_list_t *vl)
            "%f. That is %s the %s threshold of %f.",
            ds->ds[ds_index].name, values[ds_index],
            isnan (min) ? "below" : "above",
-           (state_new == STATE_ERROR) ? "failure" : "warning",
+           (state == STATE_ERROR) ? "failure" : "warning",
            isnan (min) ? max : min);
       }
     }
@@ -668,7 +663,7 @@ int ut_check_threshold (const data_set_t *ds, const value_list_t *vl)
          "%f. That is %s the %s threshold of %f.",
          ds->ds[ds_index].name, values[ds_index],
          (values[ds_index] < min) ? "below" : "above",
-         (state_new == STATE_ERROR) ? "failure" : "warning",
+         (state == STATE_ERROR) ? "failure" : "warning",
          (values[ds_index] < min) ? min : max);
     }
     buf += status;
@@ -677,10 +672,163 @@ int ut_check_threshold (const data_set_t *ds, const value_list_t *vl)
 
   plugin_dispatch_notification (&n);
 
+  return (0);
+} /* }}} int ut_report_state */
+
+/*
+ * int ut_check_one_data_source
+ *
+ * Checks one data source against the given threshold configuration. If the
+ * `DataSource' option is set in the threshold, and the name does NOT match,
+ * `okay' is returned. If the threshold does match, its failure and warning
+ * min and max values are checked and `failure' or `warning' is returned if
+ * appropriate.
+ * Does not fail.
+ */
+static int ut_check_one_data_source (const data_set_t *ds,
+    const value_list_t *vl,
+    const threshold_t *th,
+    const gauge_t *values,
+    int ds_index)
+{ /* {{{ */
+  const char *ds_name;
+  int is_warning = 0;
+  int is_failure = 0;
+
+  /* check if this threshold applies to this data source */
+  ds_name = ds->ds[ds_index].name;
+  if ((th->data_source[0] != 0)
+      && (strcmp (ds_name, th->data_source) != 0))
+    return (STATE_OKAY);
+
+  if ((th->flags & UT_FLAG_INVERT) != 0)
+  {
+    is_warning--;
+    is_failure--;
+  }
+
+  if ((!isnan (th->failure_min) && (th->failure_min > values[ds_index]))
+      || (!isnan (th->failure_max) && (th->failure_max < values[ds_index])))
+    is_failure++;
+  if (is_failure != 0)
+    return (STATE_ERROR);
+
+  if ((!isnan (th->warning_min) && (th->warning_min > values[ds_index]))
+      || (!isnan (th->warning_max) && (th->warning_max < values[ds_index])))
+    is_warning++;
+  if (is_warning != 0)
+    return (STATE_WARNING);
+
+  return (STATE_OKAY);
+} /* }}} int ut_check_one_data_source */
+
+/*
+ * int ut_check_one_threshold
+ *
+ * Checks all data sources of a value list against the given threshold, using
+ * the ut_check_one_data_source function above. Returns the worst status,
+ * which is `okay' if nothing has failed.
+ * Returns less than zero if the data set doesn't have any data sources.
+ */
+static int ut_check_one_threshold (const data_set_t *ds,
+    const value_list_t *vl,
+    const threshold_t *th,
+    const gauge_t *values,
+    int *ret_ds_index)
+{ /* {{{ */
+  int ret = -1;
+  int ds_index = -1;
+  int i;
+
+  for (i = 0; i < ds->ds_num; i++)
+  {
+    int status;
+
+    status = ut_check_one_data_source (ds, vl, th, values, i);
+    if (ret < status)
+    {
+      ret = status;
+      ds_index = i;
+    }
+  } /* for (ds->ds_num) */
+
+  if (ret_ds_index != NULL)
+    *ret_ds_index = ds_index;
+
+  return (ret);
+} /* }}} int ut_check_one_threshold */
+
+/*
+ * int ut_check_threshold (PUBLIC)
+ *
+ * Gets a list of matching thresholds and searches for the worst status by one
+ * of the thresholds. Then reports that status using the ut_report_state
+ * function above. 
+ * Returns zero on success and if no threshold has been configured. Returns
+ * less than zero on failure.
+ */
+int ut_check_threshold (const data_set_t *ds, const value_list_t *vl)
+{ /* {{{ */
+  threshold_t *th;
+  gauge_t *values;
+  int status;
+
+  int worst_state = -1;
+  threshold_t *worst_th = NULL;
+  int worst_ds_index = -1;
+
+  if (threshold_tree == NULL)
+    return (0);
+
+  /* Is this lock really necessary? So far, thresholds are only inserted at
+   * startup. -octo */
+  pthread_mutex_lock (&threshold_lock);
+  th = threshold_search (ds, vl);
+  pthread_mutex_unlock (&threshold_lock);
+  if (th == NULL)
+    return (0);
+
+  DEBUG ("ut_check_threshold: Found matching threshold(s)");
+
+  values = uc_get_rate (ds, vl);
+  if (values == NULL)
+    return (0);
+
+  while (th != NULL)
+  {
+    int ds_index = -1;
+
+    status = ut_check_one_threshold (ds, vl, th, values, &ds_index);
+    if (status < 0)
+    {
+      ERROR ("ut_check_threshold: ut_check_one_threshold failed.");
+      sfree (values);
+      return (-1);
+    }
+
+    if (worst_state < status)
+    {
+      worst_state = status;
+      worst_th = th;
+      worst_ds_index = ds_index;
+    }
+
+    th = th->next;
+  } /* while (th) */
+
+  status = ut_report_state (ds, vl, worst_th, values,
+      worst_ds_index, worst_state);
+  if (status != 0)
+  {
+    ERROR ("ut_check_threshold: ut_report_state failed.");
+    sfree (values);
+    return (-1);
+  }
+
   sfree (values);
 
   return (0);
-} /* int ut_check_threshold */
+} /* }}} int ut_check_threshold */
 
 int ut_check_interesting (const char *name)
 {
diff --git a/src/vmem.c b/src/vmem.c
new file mode 100644 (file)
index 0000000..5341e15
--- /dev/null
@@ -0,0 +1,284 @@
+/**
+ * collectd - src/vmem.c
+ * Copyright (C) 2008  Florian octo Forster
+ *
+ * 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; only version 2 of the License is applicable.
+ *
+ * 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 St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#if KERNEL_LINUX
+static const char *config_keys[] =
+{
+  "Verbose"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+
+static int verbose_output = 0;
+/* #endif KERNEL_LINUX */
+
+#else
+# error "No applicable input method."
+#endif /* HAVE_LIBSTATGRAB */
+
+static void submit (const char *plugin_instance, const char *type,
+    const char *type_instance, value_t *values, int values_len)
+{
+  value_list_t vl = VALUE_LIST_INIT;
+
+  vl.values = values;
+  vl.values_len = values_len;
+
+  vl.time = time (NULL);
+  sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+  sstrncpy (vl.plugin, "vmem", sizeof (vl.plugin));
+  if (plugin_instance != NULL)
+    sstrncpy (vl.plugin_instance, plugin_instance, sizeof (vl.plugin_instance));
+  if (type_instance != NULL)
+    sstrncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
+
+  plugin_dispatch_values (type, &vl);
+} /* void vmem_submit */
+
+static void submit_two (const char *plugin_instance, const char *type,
+    const char *type_instance, counter_t c0, counter_t c1)
+{
+  value_t values[2];
+
+  values[0].counter = c0;
+  values[1].counter = c1;
+
+  submit (plugin_instance, type, type_instance, values, 2);
+} /* void submit_one */
+
+static void submit_one (const char *plugin_instance, const char *type,
+    const char *type_instance, value_t value)
+{
+  submit (plugin_instance, type, type_instance, &value, 1);
+} /* void submit_one */
+
+static int vmem_config (const char *key, const char *value)
+{
+  if (strcasecmp ("Verbose", key) == 0)
+  {
+    if ((strcasecmp ("true", value) == 0)
+       || (strcasecmp ("yes", value) == 0)
+       || (strcasecmp ("on", value) == 0))
+      verbose_output = 1;
+    else
+      verbose_output = 0;
+  }
+  else
+  {
+    return (-1);
+  }
+
+  return (0);
+} /* int vmem_config */
+
+static int vmem_read (void)
+{
+#if KERNEL_LINUX
+  counter_t pgpgin = 0;
+  counter_t pgpgout = 0;
+  int pgpgvalid = 0;
+
+  counter_t pswpin = 0;
+  counter_t pswpout = 0;
+  int pswpvalid = 0;
+
+  counter_t pgfault = 0;
+  counter_t pgmajfault = 0;
+  int pgfaultvalid = 0;
+
+  FILE *fh;
+  char buffer[1024];
+
+  fh = fopen ("/proc/vmstat", "r");
+  if (fh == NULL)
+  {
+    char errbuf[1024];
+    ERROR ("vmem plugin: fopen (/proc/vmstat) failed: %s",
+       sstrerror (errno, errbuf, sizeof (errbuf)));
+    return (-1);
+  }
+
+  while (fgets (buffer, sizeof (buffer), fh) != NULL)
+  {
+    char *fields[4];
+    int fields_num;
+    char *key;
+    char *endptr;
+    counter_t counter;
+    gauge_t gauge;
+
+    fields_num = strsplit (buffer, fields, STATIC_ARRAY_SIZE (fields));
+    if (fields_num != 2)
+      continue;
+
+    key = fields[0];
+
+    endptr = NULL;
+    counter = strtoll (fields[1], &endptr, 10);
+    if (fields[1] == endptr)
+      continue;
+
+    endptr = NULL;
+    gauge = strtod (fields[1], &endptr);
+    if (fields[1] == endptr)
+      continue;
+
+    /* 
+     * Number of pages
+     *
+     * The total number of {inst} pages, e. g dirty pages.
+     */
+    if (strncmp ("nr_", key, strlen ("nr_")) == 0)
+    {
+      char *inst = key + strlen ("nr_");
+      value_t value = { .gauge = gauge };
+      submit_one (NULL, "vmpage_number", inst, value);
+    }
+
+    /* 
+     * Page in and page outs. For memory and swap.
+     */
+    else if (strcmp ("pgpgin", key) == 0)
+    {
+      pgpgin = counter;
+      pgpgvalid |= 0x01;
+    }
+    else if (strcmp ("pgpgout", key) == 0)
+    {
+      pgpgout = counter;
+      pgpgvalid |= 0x02;
+    }
+    else if (strcmp ("pswpin", key) == 0)
+    {
+      pswpin = counter;
+      pswpvalid |= 0x01;
+    }
+    else if (strcmp ("pswpout", key) == 0)
+    {
+      pswpout = counter;
+      pswpvalid |= 0x02;
+    }
+
+    /*
+     * Pagefaults
+     */
+    else if (strcmp ("pgfault", key) == 0)
+    {
+      pgfault = counter;
+      pgfaultvalid |= 0x01;
+    }
+    else if (strcmp ("pgmajfault", key) == 0)
+    {
+      pgmajfault = counter;
+      pgfaultvalid |= 0x02;
+    }
+
+    /*
+     * Skip the other statistics if verbose output is disabled.
+     */
+    else if (verbose_output == 0)
+      continue;
+
+    /*
+     * Number of page allocations, refills, steals and scans. This is collected
+     * ``per zone'', i. e. for DMA, DMA32, normal and possibly highmem.
+     */
+    else if (strncmp ("pgalloc_", key, strlen ("pgalloc_")) == 0)
+    {
+      char *inst = key + strlen ("pgalloc_");
+      value_t value  = { .counter = counter };
+      submit_one (inst, "vmpage_action", "alloc", value);
+    }
+    else if (strncmp ("pgrefill_", key, strlen ("pgrefill_")) == 0)
+    {
+      char *inst = key + strlen ("pgrefill_");
+      value_t value  = { .counter = counter };
+      submit_one (inst, "vmpage_action", "refill", value);
+    }
+    else if (strncmp ("pgsteal_", key, strlen ("pgsteal_")) == 0)
+    {
+      char *inst = key + strlen ("pgsteal_");
+      value_t value  = { .counter = counter };
+      submit_one (inst, "vmpage_action", "steal", value);
+    }
+    else if (strncmp ("pgscan_kswapd_", key, strlen ("pgscan_kswapd_")) == 0)
+    {
+      char *inst = key + strlen ("pgscan_kswapd_");
+      value_t value  = { .counter = counter };
+      submit_one (inst, "vmpage_action", "scan_kswapd", value);
+    }
+    else if (strncmp ("pgscan_direct_", key, strlen ("pgscan_direct_")) == 0)
+    {
+      char *inst = key + strlen ("pgscan_direct_");
+      value_t value  = { .counter = counter };
+      submit_one (inst, "vmpage_action", "scan_direct", value);
+    }
+
+    /*
+     * Page action
+     *
+     * number of pages moved to the active or inactive lists and freed, i. e.
+     * removed from either list.
+     */
+    else if (strcmp ("pgfree", key) == 0)
+    {
+      value_t value  = { .counter = counter };
+      submit_one (NULL, "vmpage_action", "free", value);
+    }
+    else if (strcmp ("pgactivate", key) == 0)
+    {
+      value_t value  = { .counter = counter };
+      submit_one (NULL, "vmpage_action", "activate", value);
+    }
+    else if (strcmp ("pgdeactivate", key) == 0)
+    {
+      value_t value  = { .counter = counter };
+      submit_one (NULL, "vmpage_action", "deactivate", value);
+    }
+  } /* while (fgets) */
+
+  fclose (fh);
+  fh = NULL;
+
+  if (pgfaultvalid == 0x03)
+    submit_two (NULL, "vmpage_faults", NULL, pgfault, pgmajfault);
+
+  if (pgpgvalid == 0x03)
+    submit_two (NULL, "vmpage_io", "memory", pgpgin, pgpgout);
+
+  if (pswpvalid == 0x03)
+    submit_two (NULL, "vmpage_io", "swap", pswpin, pswpout);
+#endif /* KERNEL_LINUX */
+
+  return (0);
+} /* int vmem_read */
+
+void module_register (void)
+{
+  plugin_register_config ("vmem", vmem_config,
+      config_keys, config_keys_num);
+  plugin_register_read ("vmem", vmem_read);
+} /* void module_register */
+
+/* vim: set sw=2 sts=2 ts=8 : */
index e0db73b..0049f7d 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-DEFAULT_VERSION="4.3.3.git"
+DEFAULT_VERSION="4.4.2.git"
 
 VERSION="$( git describe 2> /dev/null | sed -e 's/^collectd-//' )"