package Munin;
# -*- perl -*-
#
# Copyright (C) 2003-2004 Jimmy Olsen, Audun Ytterdal
#
# 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; version 2 dated June,
# 1991.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
#
#
# $Id: Munin.pm.in 1599 2008-04-26 20:56:24Z matthias $
#

use Exporter;
@ISA = ('Exporter');
@EXPORT = ('munin_trend', 
	   'munin_fetch', 
	   'munin_nscasend', 
	   'munin_createlock',
	   'munin_removelock',
	   'munin_runlock',
	   'munin_getlock',
	   'munin_readconfig',
	   'munin_writeconfig',
	   'munin_delete',
	   'munin_overwrite',
	   'munin_config',
	   'munin_draw_field',
	   'munin_get_bool',
	   'munin_get_bool_val',
	   'munin_get',
	   'munin_field_status',
	   'munin_service_status',
	   'munin_node_status',
	   'munin_category_status',
	   'munin_get_picture_filename',
	   'munin_get_filename',
	   'munin_graph_column_headers',
	   'munin_get_max_label_length',
	   'munin_get_field_order',
	   'munin_get_rrd_filename'
	   );

use strict;
use RRDs;
use Fcntl qw(:DEFAULT :flock);
use IO::Handle;
use Symbol 'gensym';

my $VERSION = "1.2.6";

my $nsca = new IO::Handle;
my $config = undef;

my $DEBUG=0;
my $configfile="/etc/munin/munin.conf";

my @legal = ("tmpldir", "ncsa", "ncsa_server", "ncsa_config", "rundir",
	"dbdir", "logdir", "htmldir", "include", "domain_order", "node_order", 
	"graph_order", "graph_sources", "fork", "graph_title", "create_args", 
	"graph_args", "graph_vlabel", "graph_vtitle", "graph_total", 
	"graph_scale", "graph", "update", "host_name", "label", "cdef", "draw", 
	"graph", "max", "min", "negative", "skipdraw", "type", "warning", 
	"critical", "special_stack", "special_sum", "stack", "sum", "address", 
	"htaccess", "warn", "use_default_name", "use_node_name", "port", 
	"graph_noscale", "nsca", "nsca_server", "nsca_config", "extinfo", 
	"fetch_data", "filename", "max_processes", "nagios", "info", 
	"graph_info", "graph_category", "graph_strategy", "graph_width", 
	"graph_height", "graph_sums", "local_address", "compare",
	"text", "command", "contact", "contacts",  "max_messages", 
	"always_send", "notify_alias", "line", "state", "graph_period",
	"cgiurl_graph", "cgiurl", "service_order", "category_order",
	"version", "colour", "ok", "unknown"
    );

my %legal_expanded = map { $_ => 1 } @legal;

# Fields to copy when "aliasing" a field
my @copy_fields    = ("label", "draw", "type", "rrdfile", "fieldname", "info"); 


sub munin_trend {
    my (@array) = @_;
    return ($array[$#array] - $array[0]);
}

sub munin_fetch {
    my ($file,$last,$type) = @_;
    my ($start,$step,$names,$data) = RRDs::fetch $file,$type || "AVERAGE";
    unless (defined $data)
    {
        ::logger ("Could not fetch data from $file(".($type||"AVERAGE")."): ". RRDs::error);
        return undef;
    }
    my @array = map { @$_[0] } splice(@$data, $#$data - ($last || 1));
    return $array[0] if (!$last);
    return @array;
}

sub munin_draw_field {
    my $node    = shift;
    my $service = shift;
    my $field   = shift;

    $field =~ s/=.*//;

    print "DEBUG: munin_draw_field: Checking $service -> $field: " . &munin_get_bool_val ($node->{client}->{$service}->{$field.".graph"}, 1) . ".\n" if $DEBUG;;
    return 0 if (exists $node->{client}->{$service}->{$field.".skipdraw"});
    return (&munin_get_bool_val ($node->{client}->{$service}->{$field.".graph"}, 1));
}

sub munin_nscasend {
    my ($name,$service,$label,$level,$comment) = @_;

    if (!$nsca->opened)
    {
	open ($nsca ,"|$config->{nsca} $config->{nsca_server} -c $config->{nsca_config} -to 60");
    }
    if ($label)
    {
	print $nsca  "$name\t$service: $label\t$level\t$comment\n";
	print ("$name;$service: $label;$level;$comment\n") if $DEBUG;
    }
    else
    {
	print $nsca  "$name\t$service\t$level\t$comment\n";
	print ("$name;$service;$level;$comment\n") if $DEBUG;
    }
}

sub munin_createlock {
    # Create lock file, fail and die if not possible.
    my ($lockname) = @_;
    if (sysopen (LOCK,$lockname,O_WRONLY | O_CREAT | O_EXCL)) {
	print "Creating lock : $lockname succeded\n" if $DEBUG;
	print LOCK $$; # we want the pid inside for later use
	close LOCK;
	return 1;
    } else {
	die "Creating lock $lockname failed: $!\n";
    }
}

sub munin_removelock {
    # Remove lock or die trying.
    my ($lockname) = @_;

    unlink $lockname or
      die "Error deleting lock $lockname: $!\n";
}

sub munin_runlock {
    my ($lockname) = @_;
    unless (&munin_getlock($lockname)) {
	print "Lock already exists: $lockname. Dying.\n";
	exit 0;
    }
    return 1;
}

sub munin_getlock {
    my ($lockname) = @_;
    if (-f $lockname) {
	# Is the lockpid alive?

	# To check this is inteligent and so on.  It also makes for a
	# nice locking racing-condition.  BUT, since munin-* runs from
	# cron every 5 minutes this should not be a real threat.  This
	# ream of code should complete in less than 5 minutes.

	open LOCK,$lockname or 
	  die "Could not open $lockname for reading: $!\n";
	my $pid = <LOCK>;
	close(LOCK) or die "Could not close $lockname: $!\n";
        # Make sure it's a proper pid
	if (defined($pid) and $pid =~ /^(\d+)$/ and $1 != 1) {
	    $pid = $1;
	    if (kill(0, $pid)) {
	        return 0;
	    }
	}
	&munin_removelock($lockname);
    }
    &munin_createlock($lockname);
    return 1;
}

sub munin_delete {
    my ($config,$data) = @_;
    for my $domain (keys %{$data->{domain}}) {
	unless ($config->{domain}->{$domain}) {
	    ::logger("Removing domain: $domain");
	    delete ($data->{domain}->{$domain});
	    next;
	}
	for my $node (keys %{$data->{domain}->{$domain}->{node}}) {
	    unless ($config->{domain}->{$domain}->{node}->{$node}) {
		::logger("Removing node from $domain: $node");
		delete ($data->{domain}->{$domain}->{node}->{$node});
	    }
	}
    }
    return ($data);
}
sub munin_overwrite {
    my ($configfile,$overwrite) = @_;
    for my $key (keys %$overwrite) {
	if (ref $overwrite->{$key}) {
	    &munin_overwrite($overwrite->{$key},$configfile->{$key});
	}
	$configfile->{$key} = $overwrite->{$key};
    }
    return ($configfile);
}

sub munin_readconfig {
    my ($conf, $missingok, $corruptok) = @_;
    my $config   = undef;
    my @contents = undef;

    $conf ||= $configfile;
    if (! -r $conf and ! $missingok) {
		::logger ("munin_readconfig: cannot open '$conf'");
		return undef;
    }
    if (open (CFG, $conf))
    {
	@contents = <CFG>;
	close (CFG);
    }

    $config = &munin_parse_config (\@contents);

    # Some important defaults before we return...
    $config->{'rundir'} ||= "/tmp/";
    $config->{'dbdir'}  ||= "/var/lib/munin/";
    $config->{'logdir'} ||= "/var/log/";
    $config->{'tmpldir'}||= "/etc/munin/templates/";
    $config->{'htmldir'}||= "/var/www/htdocs/munin/";
    return ($config);
}

sub munin_parse_config
{
    my $lines    = shift;
    my $hash     = undef;
    my $prefix   = "";
    my $prevline = "";

    foreach my $line (@{$lines})
    {
	chomp $line;
#$line =~ s/(^|[^\\])#.*/$1/g if $line =~ /#/;  # Skip comments...
	if ($line =~ /#/)
	{ 
	    next if ($line =~ /^#/);
	    $line =~ s/(^|[^\\])#.*/$1/g;
	}
	next unless ($line =~ /\S/);  # And empty lines...
	if (length $prevline)
	{
	    $line = $prevline . $line;
	    $prevline = "";
	}
	if ($line =~ /\\\\$/)
	{
	    $line =~ s/\\\\$/\\/;
	}
	elsif ($line =~ /\\$/)
	{
	    ($prevline = $line) =~ s/\\$//;
	    next;
	}
	$line =~ s/\s+$//g;           # And trailing whitespace...
	$line =~ s/^\s+//g;           # And heading whitespace...

	if ($line =~ /^\.(\S+)\s+(.+)\s*$/)
	{
	    my ($var, $val) = ($1, $2);
	    $hash = &munin_set_var_path ($hash, $var, $val);
	}
	elsif ($line =~ /^\s*\[([^\]]*)]\s*$/)
	{
	    $prefix = $1;
	    if ($prefix =~ /^([^:;]+);([^:;]+)$/)
	    {
		$prefix .= ":";
	    }
	    elsif ($prefix =~ /^([^:;]+);$/)
	    {
		$prefix .= "";
	    }
	    elsif ($prefix =~ /^([^:;]+);([^:;]+):(.*)$/)
	    {
		$prefix .= ".";
	    }
	    elsif ($prefix =~ /^([^:;]+)$/)
	    {
		(my $domain = $prefix) =~ s/^[^\.]+\.//;
		$prefix = "$domain;$prefix:";
	    }
	    elsif ($prefix =~ /^([^:;]+):(.*)$/)
	    {
		(my $domain = $prefix) =~ s/^[^\.]+\.//;
		$prefix = "$domain;$prefix.";
	    }
	}
	elsif ($line =~ /^\s*(\S+)\s+(.+)\s*$/)
	{
	    my ($var, $val) = ($1, $2);
	    $hash = &munin_set_var_path ($hash, "$prefix$var", $val);
	}
	else
	{
	    warn "Malformed configuration line \"$line\".";
	}
    }

    return $hash;
}

sub munin_get_var_path
{
    my $hash = shift;
    my $var  = shift;
    my $val  = shift;

    print "DEBUG: Getting var \"$var\" = \"$val\"\n" if $DEBUG;
    if ($var =~ /^\s*([^;:]+);([^;:]+):(\S+)\s*$/)
    {
	my ($dom, $host, $rest) = ($1, $2, $3);
	my @sp = split (/\./, $rest);

	if (@sp == 3)
	{
	    return $hash->{domain}->{$dom}->{node}->{$host}->{client}->{$sp[0]}->{"$sp[1].$sp[2]"};
	}
	elsif (@sp == 2)
	{
	    return $hash->{domain}->{$dom}->{node}->{$host}->{client}->{$sp[0]}->{$sp[1]};
	}
	elsif (@sp == 1)
	{
	    return $hash->{domain}->{$dom}->{node}->{$host}->{$sp[0]};
	}
	else
	{
	    warn "munin_set_var: Malformatted variable path \"$var\".";
	}
    }
    elsif ($var =~ /^\s*([^;:]+);([^;:]+)\s*$/)
    {
	my ($dom, $rest) = ($1, $2);
	my @sp = split (/\./, $rest);

	if (@sp == 1)
	{
	    return $hash->{domain}->{$dom}->{$sp[0]};
	}
	else
	{
	    warn "munin_set_var: Malformatted variable path \"$var\".";
	}
    }
    elsif ($var =~ /^\s*([^;:\.]+)\s*$/)
    {
	return $hash->{$1};
    }
    else
    {
	warn "munin_set_var: Malformatted variable path \"$var\".";
    }

    return undef;
}

sub munin_set_var_path
{
    my $hash = shift;
    my $var  = shift;
    my $val  = shift;

    print "DEBUG: Setting var \"$var\" = \"$val\"\n" if $DEBUG;
    if ($var =~ /^\s*([^;:]+);([^:]+):(\S+)\s*$/)
    {
	my ($dom, $host, $rest) = ($1, $2, $3);
	my @sp = split (/\./, $rest);

	if (@sp == 3)
	{
	    ::logger ("Warning: Unknown option \"$sp[2]\" in \"$dom;$host:$sp[0].$sp[1].$sp[2]\".")
		unless defined $legal_expanded{$sp[2]};
	    $hash->{domain}->{$dom}->{node}->{$host}->{client}->{$sp[0]}->{"$sp[1].$sp[2]"} = $val;
	}
	elsif (@sp == 2)
	{
	    ::logger ("Warning: Unknown option \"$sp[1]\" in \"$dom;$host:$sp[0].$sp[1]\".")
		unless defined $legal_expanded{$sp[1]};
	    $hash->{domain}->{$dom}->{node}->{$host}->{client}->{$sp[0]}->{$sp[1]} = $val;
	}
	elsif (@sp == 1)
	{
	    ::logger ("Warning: Unknown option \"$sp[0]\" in \"$dom;$host:$sp[0]\".")
		unless defined $legal_expanded{$sp[0]};
	    $hash->{domain}->{$dom}->{node}->{$host}->{$sp[0]} = $val;
	}
	else
	{
	    warn "munin_set_var: Malformatted variable path \"$var\".";
	}
    }
    elsif ($var =~ /^\s*([^;:]+);([^;:]+)\s*$/)
    {
	my ($dom, $rest) = ($1, $2);
	my @sp = split (/\./, $rest);

	if (@sp == 1)
	{
	    ::logger ("Warning: Unknown option \"$sp[0]\" in \"$dom;$sp[0]\".")
		unless defined $legal_expanded{$sp[0]};
	    $hash->{domain}->{$dom}->{$sp[0]} = $val;
	}
	else
	{
	    warn "munin_set_var: Malformatted variable path \"$var\".";
	}
    }
    elsif ($var =~ /^\s*([^;:\.]+)\s*$/)
    {
	::logger ("Warning: Unknown option \"$1\" in \"$1\".")
	    unless defined $legal_expanded{$1};
	$hash->{$1} = $val;
    }
    elsif ($var =~ /^\s*([^\.]+)\.([^\.]+)\.([^\.]+)$/)
    {
	::logger ("Warning: Unknown option \"$1\" in \"$var\".")
	    unless defined $legal_expanded{$1};
	::logger ("Warning: Unknown option \"$3\" in \"$var\".")
	    unless defined $legal_expanded{$3};
	$hash->{$1}->{$2}->{$3} = $val;
    }
    else
    {
	warn "munin_set_var: Malformatted variable path \"$var\".";
    }

    return $hash;
}

sub munin_writeconfig_loop {
    my ($data,$fh,$pre) = @_;
    $pre |= "";

    # Write datafile
    foreach my $a (keys %{$data})
    {
	if (ref ($data->{$a}) eq "HASH")
	{
	    if ($a eq "domain" or $a eq "node" or $a eq "client")
	    {
		&munin_writeconfig_loop ($data->{$a}, $fh, "$pre");
	    }
	    elsif ($a eq "contact" and $pre eq "")
	    {
		&munin_writeconfig_loop ($data->{$a}, $fh, "contact.");
	    }
	    else
	    {
		my $lpre = $pre;
		if ($lpre eq "")
		{
		    $lpre = $a.";";
		}
		elsif ($lpre =~ /;$/)
		{
		    $lpre .= $a.":";
		}
		else
		{
		    $lpre .= $a.".";
		}
		&munin_writeconfig_loop ($data->{$a}, $fh, "$lpre");
	    }
	}
	elsif (defined $data->{$a} and length $data->{$a})
	{
	    next if "$pre$a" eq "version"; # Handled separately
	    print "Writing: $pre$a $data->{$a}\n" if $DEBUG;
	    if ($data->{$a} =~ /\\$/)
	    { # Backslash as last char has special meaning. Avoid it.
		print $fh "$pre$a $data->{$a}\\\n"; 
	    } else {
		print $fh "$pre$a $data->{$a}\n";
	    }
	}
    }
}
sub munin_writeconfig {
    my ($datafilename,$data,$fh) = @_;
#   my $datafile = new Config::General();
#   $datafile->save_file($datafilename,$data);

    if (!defined $fh)
    {
	$fh = gensym();
	unless (open ($fh, ">$datafilename"))
	{
	    die "Fatal error: Could not open \"$datafilename\" for writing: $!";
	}
    }

    # Write version
    print $fh "version $VERSION\n";
    # Write datafile
    &munin_writeconfig_loop ($data, $fh, "");
    
    if (defined $fh)
    {
	print "DEBUG: Closing filehandle \"$datafilename\"...\n" if $DEBUG;
	close ($fh);
    }
}
    
sub munin_config {
    my $conffile = shift;
    $config = shift;
    $conffile ||= $configfile;
    $config = &munin_readconfig ($conffile);
    ::logger_open ($config->{logdir});
    my $data = &munin_readconfig("$config->{dbdir}/datafile", 1, 1);
    
    $data = &munin_overwrite($data,$config);
    return ($data);
}

sub munin_get_picture_filename {
    my $config  = shift;
    my $domain  = shift;
    my $name    = shift;
    my $service = shift;
    my $scale   = shift;
    my $sum     = shift;
    my $dir     = $config->{'htmldir'};

    # Sanitise
    $dir =~ s/[^\w_\/"'\[\]\(\)+=-]\./_/g;
    $domain =~ s/[^\w_\/"'\[\]\(\)+=\.-]/_/g;
    $name =~ s/[^\w_\/"'\[\]\(\)+=\.-]/_/g;
    $service =~ s/[^\w_\/"'\[\]\(\)+=-]/_/g;
    $scale =~ s/[^\w_\/"'\[\]\(\)+=-]/_/g;

    if (defined $sum and $sum)
    {
	    return "$dir/$domain/$name-$service-$scale-sum.png";
    }
    else
    {
	    return "$dir/$domain/$name-$service-$scale.png";
    }
}

sub munin_get_filename {
	my ($config,$domain,$node,$service,$field) = @_;

	return ($config->{'dbdir'} . "/$domain/$node-$service-$field-" . lc substr (($config->{domain}->{$domain}->{node}->{$node}->{client}->{$service}->{$field.".type"}||"GAUGE"), 0,1). ".rrd");

}

sub munin_get_bool
{
    my $conf     = shift;
    my $field    = shift;
    my $default  = shift;
    my $domain   = shift;
    my $node     = shift;
    my $service  = shift;
    my $plot     = shift;

    return undef unless defined $field;

    my $ans = &munin_get ($conf, $field, $default, $domain, $node, $service, $plot);
    return undef if not defined $ans;

    if ($ans =~ /^yes$/i or
        $ans =~ /^true$/i or
        $ans =~ /^on$/i or
        $ans =~ /^enable$/i or
        $ans =~ /^enabled$/i
       )
    {
    return 1;
    }
    elsif ($ans =~ /^no$/i or
        $ans =~ /^false$/i or
        $ans =~ /^off$/i or
        $ans =~ /^disable$/i or
        $ans =~ /^disabled$/i
      )
    {
    return 0;
    }
    elsif ($ans !~ /\D/)
    {
    return $ans;
    }
    else
    {
    return undef;
    }
}

sub munin_get_bool_val
{
    my $field    = shift;
    my $default  = shift;

    if (!defined $field)
    {
    if (!defined $default)
    {
        return 0;
    }
    else
    {
        return $default;
    }
    }

    if ($field =~ /^yes$/i or
        $field =~ /^true$/i or
        $field =~ /^on$/i or
        $field =~ /^enable$/i or
        $field =~ /^enabled$/i
       )
    {
    return 1;
    }
    elsif ($field =~ /^no$/i or
        $field =~ /^false$/i or
        $field =~ /^off$/i or
        $field =~ /^disable$/i or
        $field =~ /^disabled$/i
      )
    {
    return 0;
    }
    elsif ($field !~ /\D/)
    {
    return $field;
    }
    else
    {
    return undef;
    }
}

sub munin_get
{
    my $conf     = shift;
    my $field    = shift;
    my $default  = shift;
    my $domain   = shift;
    my $node     = shift;
    my $service  = shift;
    my $plot     = shift;

    if (defined $field)
    {
	return $conf->{domain}->{$domain}->{node}->{$node}->{client}->{$service}->{"$plot.$field"}
		if (defined $domain and defined $node and defined $service and defined $plot and 
			defined $conf->{domain}->{$domain}->{node}->{$node}->{client}->{$service}->{"$plot.$field"});

	

	return $conf->{domain}->{$domain}->{node}->{$node}->{client}->{$service}->{$field}
		if (defined $domain and defined $node and defined $service and 
			defined $conf->{domain}->{$domain}->{node}->{$node}->{client}->{$service}->{$field});
	return $conf->{domain}->{$domain}->{node}->{$node}->{$field}
		if (defined $domain and defined $node and 
			defined $conf->{domain}->{$domain}->{node}->{$node}->{$field});
	return $conf->{domain}->{$domain}->{$field}
		if (defined $domain and defined $conf->{domain}->{$domain}->{$field});
	return $conf->{$field}
		if (defined $conf->{$field});
	return $default;
    }
    else
    {
	return $conf->{domain}->{$domain}->{node}->{$node}->{client}->{$service}
		if (defined $domain and defined $node and defined $service and 
			defined $conf->{domain}->{$domain}->{node}->{$node}->{client}->{$service});
	return $conf->{domain}->{$domain}->{node}->{$node}
		if (defined $domain and defined $node and 
			defined $conf->{domain}->{$domain}->{node}->{$node});
	return $conf->{domain}->{$domain}
		if (defined $domain and defined $conf->{domain}->{$domain});
	return $conf
		if (defined $conf);
	return $default;
    }
}

sub munin_node_status
{
    my ($config, $limits, $domain, $node, $check_draw) = @_;
    my $state = "ok";

    return undef unless defined $config->{domain}->{$domain}->{node}->{$node};
    my $snode = $config->{domain}->{$domain}->{node}->{$node};

    foreach my $service (keys %{$snode})
    {
	my $fres  = &munin_service_status ($config, $limits, $domain, $node, $service, $check_draw);
	if (defined $fres)
	{
	    if ($fres eq "critical")
	    {
		$state = $fres;
		last;
	    }
	    elsif ($fres eq "warning")
	    {
		$state = $fres;
	    }
	}
    }

    return $state;
}

sub munin_category_status
{
    my ($config, $limits, $domain, $node, $category, $check_draw) = @_;
    my $state = "ok";

    return undef unless defined $config->{domain}->{$domain}->{node}->{$node};
    my $snode = $config->{domain}->{$domain}->{node}->{$node};

    foreach my $service (keys %{$snode->{client}})
    {
	next if ((not defined $snode->{client}->{$service}->{graph_category}) and
		$category ne 'other');
	next if ((defined $snode->{client}->{$service}->{graph_category}) and 
		 ($snode->{client}->{$service}->{graph_category} ne $category));

	my $fres  = &munin_service_status ($config, $limits, $domain, $node, $service, $check_draw);
	if (defined $fres)
	{
	    if ($fres eq "critical")
	    {
		$state = $fres;
		last;
	    }
	    elsif ($fres eq "warning")
	    {
		$state = $fres;
	    }
	}
    }

    return $state;
}

sub munin_service_status
{
    my ($config, $limits, $domain, $node, $service, $check_draw) = @_;
    my $state = "ok";

    return undef unless defined $config->{domain}->{$domain}->{node}->{$node}->{client}->{$service};
    foreach my $key (keys %{$config->{domain}->{$domain}->{node}->{$node}->{client}->{$service}})
    {
	next unless $key =~ /^([^\.]+)\.label$/;
	my $field = $1;
	my $fres  = &munin_field_status ($config, $limits, $domain, $node, $service, $field, $check_draw);
	if (defined $fres)
	{
	    if ($fres eq "critical")
	    {
		$state = $fres;
		last;
	    }
	    elsif ($fres eq "warning")
	    {
		$state = $fres;
	    }
	}
    }

    return $state;
}

sub munin_field_status
{
    my ($config, $limits, $domain, $node, $service, $field, $check_draw) = @_;
    my $state = undef;

    # Return undef if nagios is turned off, or the field doesn't have any limits
    unless ((defined $config->{domain}->{$domain}->{node}->{$node}->{client}->{$service}->{"$field.warning"}) or
	(defined $config->{domain}->{$domain}->{node}->{$node}->{client}->{$service}->{"$field.critical"}))
    {
	return undef;
    }

    if (defined $limits->{domain}->{$domain}->{node}->{$node}->{client}->{$service}->{"$field.critical"} and (!defined $check_draw or !$check_draw or 
		&munin_draw_field ($config->{domain}->{$domain}->{node}->{$node}, $service, $field)))
    {
	return "critical";
    }
    elsif (defined $limits->{domain}->{$domain}->{node}->{$node}->{client}->{$service}->{"$field.warning"} and (!defined $check_draw or !$check_draw or 
		&munin_draw_field ($config->{domain}->{$domain}->{node}->{$node}, $service, $field)))
    {
	return "warning";
    }
    else
    {
	return "ok";
    }
}

sub munin_graph_column_headers
{
    my ($config, $domain, $node, $serv) = @_;
    my $ret = 0;
    my @fields = ();

    foreach my $field (keys %{$config->{domain}->{$domain}->{node}->{$node}->{client}->{$serv}})
    {
	if ($field =~ /^([^\.]+)\.negative$/ and munin_draw_field ($config->{domain}->{$domain}->{node}->{$node}, $serv, $1))
	{
	    return 1;
	}
	elsif ($field =~ /^([^\.]+)\.label$/ and munin_draw_field ($config->{domain}->{$domain}->{node}->{$node}, $serv, $1))
	{
	    push @fields, $1;
	}
    }

    return 1 if (munin_get_max_label_length ($config->{'domain'}->{$domain}->{'node'}->{$node}, $config, $domain, $node, $serv, \@fields) > 20);

    return $ret;
}

sub munin_get_max_label_length
{
    my $node    = shift;
    my $config  = shift;
    my $domain  = shift;
    my $host    = shift;
    my $service = shift;
    my $order   = shift;
    my $result  = 0;

    for my $field (@$order) {
	my $path = undef;
	(my $f = $field) =~ s/=.+//;
	next if (exists $node->{client}->{$service}->{$f.".process"} and
		 $node->{client}->{$service}->{$f.".process"} ne "yes");
	next if (exists $node->{client}->{$service}->{$f.".skipdraw"});
	next unless (!exists $node->{client}->{$service}->{$f.".graph"} or
			$node->{client}->{$service}->{$f.".graph"} eq "yes");
	if ($result < length ($node->{client}->{$service}->{$f.".label"} || $f)) {
	    $result = length ($node->{client}->{$service}->{$f.".label"} || $f);
	}
	if (exists $node->{client}->{$service}->{graph_total} and 
		length $node->{client}->{$service}->{graph_total} > $result)
	{
	    $result = length $node->{client}->{$service}->{graph_total};
	}
    }
    return $result;
}

sub munin_get_field_order
{
    my $node    = shift;
    my $config  = shift;
    my $domain  = shift;
    my $host    = shift;
    my $service = shift;
    my $result  = [];

    if ($node->{client}->{$service}->{graph_sources}) 
    {
	foreach my $gs (split /\s+/, $node->{client}->{$service}->{'graph_sources'})
	{
	    push (@$result, "-".$gs);
	}
    } 
    if ($node->{client}->{$service}->{graph_order}) 
    {
	push (@$result, split /\s+/, $node->{client}->{$service}->{'graph_order'});
    } 

    for my $key (keys %{$node->{client}->{$service}}) 
    {
	my ($client,$type)="";
	($client,$type) = split /\./,$key;
	if (defined $type and $type eq "label") 
	{
	    push @$result,$client if !grep /^\Q$client\E(?:=|$)/, @$result;;
	} 
    }
    
    return $result;
}

sub munin_get_rrd_filename {
    my $node    = shift;
    my $config  = shift;
    my $domain  = shift;
    my $name    = shift;
    my $service = shift;
    my $field   = shift;
    my $path    = shift;
    my $result  = "unknown";

    if ($node->{client}->{$service}->{$field.".filename"})
    {
	$result = $node->{client}->{$service}->{$field.".filename"};
    }
    elsif ($path)
    {
	if (!defined ($node->{client}->{$service}->{$field.".label"}))
	{
	    print "DEBUG: Setting label: $field\n" if $DEBUG;
	    $node->{client}->{$service}->{$field.".label"} = $field;
	}

	if ($path =~ /^\s*([^:;]+)[:;]([^:]+):([^:\.]+)[:\.]([^:\.]+)\s*$/)
	{
	    $result = munin_get_filename ($config, $1, $2, $3, $4);
	    print "\nDEBUG1: Expanding $path...\n" if $DEBUG;
	    if (! defined $node->{client}->{$service}->{$field."label"})
	    {
		for my $f (@copy_fields)
		{
		    if (not exists $node->{client}->{$service}->{"$field.$f"} and
			    exists $config->{'domain'}->{$1}->{'node'}->{$2}->{'client'}->{$3}->{"$4.$f"})
		    {
			$node->{client}->{$service}->{"$field.$f"} = $config->{'domain'}->{$1}->{'node'}->{$2}->{'client'}->{$3}->{"$4.$f"};
		    }
		}
	    }
	}
	elsif ($path =~ /^\s*([^:]+):([^:\.]+)[:\.]([^:\.]+)\s*$/)
	{
	    print "\nDEBUG2: Expanding $path...\n" if $DEBUG;
	    $result = munin_get_filename ($config, $domain, $1, $2, $3);
	    for my $f (@copy_fields)
	    {
		if (not exists $node->{client}->{$service}->{"$field.$f"} and
			exists $config->{'domain'}->{$domain}->{'node'}->{$1}->{'client'}->{$2}->{"$3.$f"})
		{
		    print "DEBUG: Copying $f...\n" if $DEBUG;
		    $node->{client}->{$service}->{"$field.$f"} = $config->{'domain'}->{$domain}->{'node'}->{$1}->{'client'}->{$2}->{"$3.$f"};
		}
	    }
	}
	elsif ($path =~ /^\s*([^:\.]+)[:\.]([^:\.]+)\s*$/)
	{
	    print "\nDEBUG3: Expanding $path...\n" if $DEBUG;
	    $result = munin_get_filename ($config, $domain, $name, $1, $2);
	    for my $f (@copy_fields)
	    {
		if (not exists $node->{client}->{$service}->{"$field.$f"} and
			exists $node->{client}->{$1}->{"$2.$f"})
		{
		    $node->{client}->{$service}->{"$field.$f"} = $node->{client}->{$1}->{"$2.$f"};
		}
	    }
	}
	elsif ($path =~ /^\s*([^:\.]+)\s*$/)
	{
	    print "\nDEBUG4: Expanding $path...\n" if $DEBUG;
	    $result = munin_get_filename ($config, $domain, $name, $service, $1);
	    for my $f (@copy_fields)
	    {
		if (not exists $node->{client}->{$service}->{"$field.$f"} and
			exists $node->{client}->{$service}->{"$1.$f"})
		{
		    $node->{client}->{$service}->{"$field.$f"} = $node->{client}->{$service}->{"$1.$f"};
		}
	    }
	}
    }
    else
    {
	print "\nDEBUG5: Doing path...\n" if $DEBUG;
	$result = munin_get_filename($config, $domain,$name,$service,$field);
    }
    return $result;
}


1;

# vim: syntax=perl ts=8
