#
# Plugin wrapper for perl version of Nagios plugins
#
# This module provides the wrapper for writing a standard
# perl plugin. It automates the command line parsing,
# general options checking, and execution path for the
# plugin. It also gives a standard form for generating results
# and usage, help, version output.
#
# Originally written to allow the generation of machine
# parseable output as well as human readable forms.
#

package Plugin;

require 5.006;
require Exporter;
@ISA = qw(Exporter);

use strict;
use Plugin::Parameter qw(:
use Getopt::Long;

use English;

if ($PROGRAM_NAME =~ m/^(.*?)[\/\\]([^\/\\]+)$/) {
  $main::runtimedir = $1;
  $main::PROGNAME = $2;
}

my $PACKAGE = "nagios-plugins";
my $VERSION = "1.4.0alpha2";

my $COPYRIGHT = "2004 Howard Wilkinson <howard\@cohtech.com>";
my $LINELENGTH = 76;
my $SUPPORT = "Send email to nagios-users\@lists.sourceforge.net if you have questions regarding use of this software. To submit patches or suggest improvements, send email to nagiosplug-devel\@lists.sourceforge.net. Please include version information with all correspondence (when possible, use output from the --version option of the plugin itself).";
my $WARRENTY = "The nagios plugins come with ABSOLUTELY NO WARRANTY. You may redistribute copies of the plugins under the terms of the GNU General Public License. For more information about these matters, see the file named COPYING.";

use vars qw($__help $__version $__xml);
use lib utils.pm;
use utils qw(%ERRORS);

sub new {
  my ($class, %argv) = @_;
  my $this = bless { '_name'           => $main::PROGNAME,
		     '_revision'       => undef,
		     '_copyright'      => undef,
		     '_shortcomment'   => undef,
		     '_longcomment'    => undef,
		     '_support'        => undef,
		     '_warrenty'       => undef,
		     '_checker'        => undef,
		     '_parameterlists' => [] }, $class;

  foreach my $key (keys %argv) {
    if ($key =~ /^-?name$/i) {
      $this->name($argv{$key});
    } elsif ($key =~ /^-?revision$/i) {
      $this->revision($argv{$key});
    } elsif ($key =~ /^-?copyright$/i) {
      $this->copyright($argv{$key});
    } elsif ($key =~ /^-?shortcomment$/i) {
      $this->shortcomment($argv{$key});
    } elsif ($key =~ /^-?longcomment$/i) {
      $this->longcomment($argv{$key});
    } elsif ($key =~ m/^-?parameterlists$/i) {
      $this->parameterlists($argv{$key});
    } elsif ($key =~ m/^-?parameterlist$/i) {
      $this->add_parameterlist($argv{$key});
    } elsif ($key =~ m/^-?support$/i) {
      $this->support($argv{key});
    } elsif ($key =~ m/^-?warrenty$/i) {
      $this->warrenty($argv{$key});
    } elsif ($key =~ m/^-?checker$/i) {
      $this->checker($argv{$key});
    } else {
      # Structural calling error - report and exit
      utils::usage("$main::PROGNAME UNKNOWN: arg $key unknown to Plugin->new\n");
    }
  }

  return $this;
}

sub name {
  my ($this, $name) = @_;
  $this->{_name} = $name if (defined $name);
  return $this->{_name};
}

sub revision {
  my ($this, $revision) = @_;
  $this->{_revision} = $revision if (defined $revision);
  return $this->{_revision};
}

sub copyright {
  my ($this, $copyright) = @_;
  $this->{_copyright} = $copyright if (defined $copyright);
  return $this->{_copyright};
}

sub shortcomment {
  my ($this, $shortcomment) = @_;
  $this->{_shortcomment} = $shortcomment if (defined $shortcomment);
  return $this->{_shortcomment};
}

sub longcomment {
  my ($this, $longcomment) = @_;
  return $this->{_longcomment} = $longcomment if (defined $longcomment);
  return $this->{_longcomment};
}

sub parameterlists {
  my ($this, $parameterlists) = @_;
  if (defined $parameterlists) {
    if (ref($parameterlists) ne 'ARRAY') {
      utils::usage($this->name()." UNKNOWN: parameterlists supplied must be reference to an ARRAY\n");
    }
    $this->{_parameterlists} = $parameterlists;
  }
  return $this->{_parameterlists};
}

sub add_parameterlist {
  my ($this, $parameterlist) = @_;
  if (ref($parameterlist) ne 'ARRAY') {
    utils::usage($this->name()." UNKNOWN: invalid parameterlist to Plugin::add_parameterlist must be a reference to an ARRAY\n");
  }
  push @{$this->{_parameterlists}}, $parameterlist;
}

sub support {
  my ($this, $support) = @_;
  return $this->{_support} = $support if (defined $support);
  return $this->{_support};
}

sub warrenty {
  my ($this, $warrenty) = @_;
  return $this->{_warrenty} = $warrenty if (defined $warrenty);
  return $this->{_warrenty};
}

sub checker {
  my ($this, $checker) = @_;
  return $this->{_checker} = $checker if (defined $checker);
  return $this->{_checker};
}

sub action {
  my ($this, $action) = @_;
  return $this->{_action} = $action if (defined $action);
  return $this->{_action};
}

my %opttypes = ( HOSTNAME     => "s",
		 HOSTADDRESS  => "s",
		 STRING       => "s",
		 INTEGER      => "i",
		 TIMEOUT      => "i",
		 PORT         => "s",
	         FILENAME     => "s",
	         PATH         => "s",
		 SMBPATH      => "s",
		 TEMPERATURE  => "i",
		 MINUTES      => "i",
		 SECONDS      => "i",
		 PERCENT      => "s",
	         COMMUNITY    => "s",
		 THRESHOLD    => "s",
		 DIRECTORY    => "s",
	         ASNUMBER     => "i",
		 USER         => "s",
		 PASSWORD     => "s",
	         WORKGROUP    => "s",
	         AUTHPROTOCOL => "s",
	         PRIVPROTOCOL => "s",
	         SNMPVERSION  => "s" );

# Process the Plugin structure setting up the environment for the run-time operation
sub setup {
  my ($this) = @_;
  my %options = ();
  foreach my $parameterlist (@{$this->parameterlists()}) {
    foreach my $parameter (@{$parameterlist}) {
      my $options = "";
      my $prefix = "";
      my $binding = $parameter->binding();
      if ($parameter->flags()) {
	foreach my $flag (@{$parameter->flags()}) {
	  $options .= $prefix . $flag;
	  $prefix = "|";
	  if (!$binding) {
	    $binding = eval ("\\\$main::opt_$flag");
	    $parameter->binding($binding);
	  }
	}
      }
      unless ($binding) {
	$binding = eval ("\\\$main::opt_".$parameter->name());
	$parameter->binding($binding);
      }
      if ($parameter->type() && $parameter->type() ne "NONE") {
	my $opttype = "s";
	$opttype = ($opttypes{$parameter->type()})?$opttypes{$parameter->type()}:"s";
	$options .= (($parameter->valueoptional)?':':'=') . $opttype;
      }
      $options{$options} = $binding;
    }
  }
  $this->{_options} = \%options;
}

# Initialise the run time environment
sub init {
  my ($this) = @_;
  # Run the GetOptions operation.
  Getopt::Long::Configure('auto_abbrev', 'bundling', 'no_ignore_case');

  if (!defined $this->{_options}) {
    $this->setup();
  }

  my $result=GetOptions(%{$this->{_options}});

  unless ($result) {
    $this->usage();
    exit $ERRORS{'UNKNOWN'};
  }

  if ($__help) {
    $this->help();
    exit $ERRORS{'OK'};
  }

  if ($__version) {
    $this->version();
    exit $ERRORS{'OK'};
  }

  foreach my $parameterlist (@{$this->parameterlists()}) {
    foreach my $parameter (@{$parameterlist}) {
      if (ref($parameter->binding()) eq 'SCALAR') {
	if (!defined ${$parameter->binding()}) {
	  ${$parameter->binding()} = shift @ARGV;
	}
	if (!defined ${$parameter->binding()}) {
	  ${$parameter->binding()} = $parameter->default();
	}
	if ($parameter->required() && !defined ${$parameter->binding()}) {
	  $this->usage();
	  my $errormsg = $parameter->message();
	  &utils::usage("$main::PROGNAME UNKNOWN: $errormsg and is not set\n");
	}
      } elsif (ref($parameter->binding()) eq 'ARRAY') {
	if (! @{$parameter->binding()}) {
	  if (@ARGV) {
	    push @{$parameter->binding()}, shift @ARGV;
	  } elsif (defined $parameter->default()) {
	    push @{$parameter->binding()}, $parameter->default;
	  } elsif ($parameter->required()) {
	    $this->usage();
	    my $errormsg = $parameter->message();
	    &utils::usage("$main::PROGNAME UNKNOWN: $errormsg and is not set\n");
	  }
	}
      } elsif (ref($parameter->binding()) eq 'HASH') {
	my $flag = (@{$parameter->flags()})
	  ?$parameter->flags->[0]
	    :$parameter->name();
	if (! $parameter->binding()->{$flag}) {
	  if (@ARGV) {
	    $parameter->binding()->{$flag} = shift @ARGV;
	  } elsif ($parameter->default()) {
	    $parameter->binding()->{$flag} = $parameter->default();
	  } elsif ($parameter->required()) {
	    $this->usage();
	    my $errormsg = $parameter->message();
	    &utils::usage("$main::PROGNAME UNKNOWN: $errormsg and is not set\n");
	  }
	}
      } elsif (ref($parameter->binding()) eq 'CODE') {
	my $arg = (@ARGV) ? shift @ARGV : $parameter->default();
	if ($arg) { 
	  &{$parameter->binding()}($arg);
	}
      }
      if ($parameter->checker()) {
	&{$parameter->checker()}($parameter->binding, $parameter, $this);
      }
    }
  }

  if (@ARGV) { # Too many arguments to the command
    $this->usage();
    &utils::usage("$main::PROGNAME UNKNOWN: Too many arguments to plugin\n");
  }

  if (defined $this->checker()) { &{$this->checker()}($this); }

}

sub run {
  my ($this) = @_;

  return $this->action()->($this) if ($this->action());
}

sub final {
  my ($this) = @_;

}

sub stop {
  my ($this) = @_;

}

sub start_timeout () {
  my ($this, $timeout, $msg) = @_;

  $SIG{'ALRM'} = sub {
    print "$main::PROGNAME UNKNOWN: $msg\n";
    exit $ERRORS{'UNKNOWN'};
  };
  alarm($timeout);
}

sub stop_timeout () {
  my ($this) = @_;

  alarm(0);
}

sub usage ($) {
  my ($this) = @_;
  
  if ($__xml) {
    _print_usage_xml($this);
  } else {
    _print_usage($this);
  }
}
  
sub help ($) {
  my ($this) = @_;
  
  if ($__xml) {
    _print_help_xml($this);
  } else {
    _print_help($this);
  }
}
  
sub version ($) {
  my ($this) = @_;

  if ($__xml) {
    # .....
    ;
  } else {
    _print_name($this->name());
    _print_revision($this->revision());
    _print_warrenty($this->warrenty());
  }
}

sub _print_usage_xml ($) {
  my ($this) = @_;

  print "<Plugin";
  printf " Name=\"%s\"", $this->name() if($this->name());;
  printf " Revision=\"%s\"", $this->revision() if ($this->revision());
  printf " Copyright=\"%s\"", $this->copyright() if ($this->copyright);;
  printf " ShortCommentary=\"%s\"", $this->shortcomment() if ($this->shortcomment());
  printf " LongCommentary=\"%s\"", $this->longcomment() if ($this->longcomment());
  printf " Support=\"%s\"", $this->support() if ($this->support());
  printf " Warrenty=\"%s\"", $this->warrenty() if ($this->warrenty());
  print " >\n";

  foreach my $parameterlist (@{$this->parameterlists()}) {
    print "  <ParameterList>\n";
    foreach my $parameter (@{$parameterlist}) {
      print "    <Parameter";
      printf " Name=\"%s\"", $parameter->name() if ($parameter->name());
      printf " Type=\"%s\"", $parameter->type() if ($parameter->type());
      printf " Mandatory=\"%s\"", $parameter->mandatory();
      printf " ValueOptional=\"%s\"", $parameter->valueoptional() if (@{$parameter->flags()});
      printf " Default=\"%s\"", $parameter->default() if ($parameter->default());
      printf " Description=\"%s\"", $parameter->description() if ($parameter->description());
      print " >\n";
      foreach my $flag (@{$parameter->flags()}) {
	printf "      <Flag>%s</Flag>\n", $flag;
      }
      print "    </Parameter>\n";
    }
    print "  </ParameterList>\n";
  }
  print "</Plugin>\n"
}

sub _print_help_xml ($) {
  my ($this) = @_;

  _print_usage_xml($this);
}

sub _print_name ($) {
  my ($name) = @_;
  printf "%s ", ($name)?$name:$main::PROGNAME;
}

sub _print_revision ($) {
  my ($revision) = @_;
  $revision = '$Revision: 1.0 $' unless ($revision);
  $revision =~ s/^\$Revision: //;
  $revision =~ s/ \$\s*$//;
  printf "(%s %s) %s\n", $PACKAGE, $VERSION, $revision;
}

sub _print_warrenty ($) {
  my ($warrenty) = @_;
  print _wrapat(($warrenty)?$warrenty:$WARRENTY, $LINELENGTH, "");
}

sub _print_copyright ($) {
  my ($copyright) = @_;
  printf "Copyright (c) %s\n", ($copyright)?$copyright:$COPYRIGHT;
}

sub _print_shortcommentary ($) {
  my ($c) = @_;
  printf "\n%s\n", _wrapat($c, $LINELENGTH, "") if($c);
}

sub _print_usage ($) {
  my ($this) = @_;

  foreach my $parameterlist (@{$this->parameterlists()}) {
    printf "Usage: %s ", $this->name();
    foreach my $parameter (@{$parameterlist}) {
      printf " ";
      if ($parameter->optional()) {
	printf "[";
      }
      if ($parameter->flags()) {
	my $flag = undef;
	foreach my $f (@{$parameter->flags()}) {
	  if ($flag && length($flag) <= length($f)) { next; }
	  $flag = $f;
	}
	printf "%s%s", (length($flag) == 1)?'-':'--', $flag;
      }
      if ($parameter->type() && $parameter->type() ne "NONE") {
	printf " %s<%s>%s",
	  ($parameter->valueoptional())?"[":"",
	    $parameter->type(),
	      ($parameter->valueoptional())?"]":"";
      }
      if ($parameter->optional()) {
	printf "] ";
      }
    }
    printf "\n";
  }
}

sub _print_fullusage ($) {
  my ($this) = @_;

  foreach my $parameterlist (@{$this->parameterlists()}) {
    print "\n";
    foreach my $parameter (@{$parameterlist}) {
      if ($parameter->optional()) {
	printf "[";
      }
      if ($parameter->flags()) {
	my $prefix = "";
	foreach my $flag (@{$parameter->flags()}) {
	  printf "%s%s%s", $prefix, (length($flag) == 1)?'-':'--', $flag;
	  $prefix = ', ';
	}
      }
      if ($parameter->type() && $parameter->type() ne "NONE") {
	printf "=%s<%s>%s",
	  ($parameter->valueoptional())?"[":"",
	    $parameter->type(),
	      ($parameter->valueoptional())?"]":"";
      }
      if ($parameter->optional()) {
	printf "]";
      }
      printf "\n";
      my $description = $parameter->description()
	. (($parameter->default())?" (Default: ".$parameter->default().")":"");
      my $output = _wrapat($description, $LINELENGTH, "\t");
      printf "%s", $output;
    }
  }
}

sub _print_longcommentary ($) {
  my ($c) = @_;
  my $output = ($c)?_wrapat($c, $LINELENGTH, ""):"";
  printf "\n%s\n", $output;
}

sub _print_support ($) {
  my ($s) = @_;
  my $output = _wrapat(($s)?$s:$SUPPORT, $LINELENGTH, "");
  printf "%s\n", $output;
}

#
# This operation prints out the help message. Low level function.
#
sub _print_help ($) {
  my ($this) = @_;

  _print_name($this->name());
  _print_revision($this->revision());
  _print_warrenty($this->warrenty());
  _print_copyright($this->copyright());
  _print_shortcommentary($this->shortcomment());
  _print_usage($this);
  _print_fullusage($this);
  _print_longcommentary($this->longcomment());
  _print_support($this->support());
}

sub _wrapat ($$$) {
  my ($report, $length, $prefix) = @_;

  my $output = "";
  while (length($report) > $length) {
    my $i = $length;
    while ($i > 0 && substr($report, $i, 1) ne ' ') { $i--; }
    if ($i <= 0) { # Just break at length
      $i = $length;
    }
    my $slice = substr($report, 0, $i);
    $output .= $prefix . $slice . "\n";
    $report = substr($report, $i+1);
  }
  return $output . $prefix. $report . "\n";
}

1;

=head1 NAME

Plugin - Provide the framework for a Nagios Plugin.

=head1 SYNOPSIS

    use Plugin;

    $plugin = new Plugin(-name          => "check_plugins",
                         -revision      => '$Revision: 1.3 $',
                         -copyright     => "2004 Howard Wilkinson E<lt>howard\@cohtech.comE<gt>",
                         -shortcomment  => "A short comment about the plugin to appear before the usage information",
                         -longcomment   => "A longer comment about the operation of the plaugin that appears after the usage information",
                         -support       => "Support for this Plugin is an illusion and can be found at E<lt>tony.blair\@number10.gov.ukE<gt>",
                         -warrenty      => "This plugin is warrented to do absolutely nothing useful and probably a lot of damage, use is at your, and everybody else's risk",
                         -checker       => sub { my ($plugin) = @_;
                                                 print "I never tell the truth\n";
                                               },
                         -parameterlists=> [ [ $H_opt, $w_opt, $c_opt, $t_opt ], $h_opts, $V_opts ]);

=head1 DESCRIPTION

This package defines the support for a Nagios Plugin.
The object produced has methods which support command line parsing and
timeout management.

Future development is likely to include error management and output support
to provide a consistent interface from a plugin.

The Plugin object has a constructor,  and accessor/updater methods, as well
as plugin phase management methods.

=head1 METHODS

When named argument are expected by the methods,  two different styles are
supported.  All examples in this documentation use the dashed-option style:

	$object->method(-argument => $value);

However, the IO:: style is also allowed:

	$object->method(Argument => $value);

=head2 new() - create a new Plugin object

    see L<SYNOPSIS>.

This is the constructor for the Plugin objects.  A reference to the Plugin
object is returned if the creation of the object is successful.  If the
creation fails then the constructor currently does not return.  However, it
will in the future returned 'undef' is such a circumstance and if called in
list context will return an error message as the second result.

On failure the current implementation call the utils::usage function
reporting an unknown option.

The arguments passed to the constructor set up attributes of the objects,
each of these can be later changed by the accessor methods.

=over

=item -name Argument

The name of the plugin can be set on the object. Currently this is not used
for very much but the intention is that this will eventually replace the
$main::PROGNAME variable.  The next release will bind the 2 variables together
allowing changes to the Plugin object to affect the underlying legacy code.

=item -revision Argument

The revision of the plaugin as supplied from the CVS repositiroty is provided
here. This is used in the output from the help and other reporting functions
and replaces the print_revision functionality in the utils package.

=item -copyright Argument

The copyright attached to the Plugin is stored here.  The string should only
include the year and the attributed names for that year.  Multiple copyright
attributions should follow separated by ',' in date order.

=item -shortcomment Argument

The shortcomment(ary) is the information provided at the head of the help
listing and should give a short description of the purpose of the Plugin.

=item -longcomment Argument

The longcomment(ary) if the information provided at the tail of the help
listing and should give information about the operation of the Plugin.
Common information includes interdependencies between arguments and
environmental reliance of the Plugin to operate.

=item -support Argument

The support argument gives details about any special support options
provided for the Plugin. The default for this attribute is the standard
support output from Plugins.

=over

Send email to nagios-users\@lists.sourceforge.net if you have questions
regarding use of this software. To submit patches or suggest improvements,
send email to nagiosplug-devel\@lists.sourceforge.net. Please include
version information with all correspondence (when possible, use output
from the --version option of the plugin itself)

=back

=item -warrenty Argument

The warrenty argument gives details of any special warrenty provided with
the Plugin. The default for this attribute is the standard warrenty output
from Plugins.

=over

The nagios plugins come with ABSOLUTELY NO WARRANTY.  You may redistribute
copies of the plugins under the terms of the GNU General Public License.
For more information about these matters, see the file named COPYING.

=back

=item -checker Argument

The checker argument provides the means to check the results of parsing the
command line arguments. Currently this is called in the init() method after
all of the parameters have been processed, allowing the interaction between
parameters to be cross-checked. The checker is called with the plugin as
its only argument and is not required to return any information.  It can
change the plugin object as it sees fit and of course play with the
environment of the Plugin as well.

Later implementations may return information from the checkers where errors
occur. Current practice is to report the first error found and exit the
Plugin via the utils::usage function.

=item -parameterlists Argument

The Plugin object has a list of parameters which it expects to find on the
passed command list.  The list is derived from a list of lists held in the
parameterlists attribute.

The 2-level structure is designed to support the multiple usage output that
Plugins can report and although not currently implemented is designed to
provide a command line checking facility that allows flags to be bound to
the previous occurence of flags.

For example, a simple command may take produce the following usage statement:

=over

Usage: -H E<lt>HOSTNAMEE<gt> -w E<lt>THRESHOLDE<gt> -c E<lt>THRESHOLDE<gt> -t E<lt>TIMEOUTE<gt>
Usage: -h {--xml}
Usage: -V {--xml}

=back

This usage indicates that the command can be given either the standard
parameters of hostname, and warning, critical thresholds, followed by a
timeout; or it can be given a help flag, optionally followed by an B<xml>
flag. See L<XML> below. Or finally it can be given a version flags,
optionally followed by an B<xml> flag.  This would be represented in a
parameterlists attribute as follows:

=over

-parameterlists => [ [ $H_opt, $w_opt, $c_opt, $t_opt ], $h_opts, $V_opts ]

=back

Where the $X_opt arguments are all Parameter object references.
See L<Parameter>) abd the $X_opts arguments are references to ARRAYs
containing references to L<Parameter> objects.

B<Currently> the Plugin object accepts any number of parameter lists to this
argument and just flattens the structure for command line parsing. This
behaviour should be considered interim and will be replaced by a parser
that allows for multiple command line options.

B<All Plugins should (read as a non-prescriptive I<must>) include the
$h_opts and $V_opts ($helpparameterlist and $versionparameterlist)
items to support standard help and version management. This set of
default lists may grow in the future but the plugin writer can protect
against such changes by using the B<@defoptlists> array exported from
the L<Parameter> module. For example:

-parameterlists => [ [ ..... ], @defoptslists ]

See L<Parameter> for more details of the object attributes used to construct
the command line parsing.

=head2 name() accessor

The Plugin name can be accessed and changed by using the name() method.

=over

$plugin->name("check_plugin");

my $name = $plugin->name();

=back

=head2 revision() accessor

The Plugin revision can be accessed and changed by using the revision()
method.

=over

$plugin->revision('$Revision: 9999.99.66.666 $');

my $revision = $plugin->revision();

=back

=head2 copyright() accessor

The Plugin copyright can be accessed and changed by using the copyright()
method.

=over

$plugin->copyright("2004 Howard Wilkinson E<lt>howard@cohtech.comE<gt>");

my $copyright = $plugin->copyright();

=back

=head2 shortcomment() accessor

The Plugin shortcomment can be accessed and changed by using the shortcomment()
method.

=over

$plugin->shortcomment("Plugin for checking plugins");

my $shortcomment = $plugin->shortcomment();

=back

=head2 longcomment() accessor

The Plugin longcomment can be accessed and changed by using the longcomment()
method.

=over

$plugin->longcomment("A long decsription about how to use this plugin with multiple ...");

my $longcomment = $plugin->longcomment();

=back

=head2 support() accessor

The Plugin support can be accessed and changed by using the support()
method.

=over

$plugin->support("Support for this plugin can be obtained by dialing 911");

my $support = $plugin->support();

=back

=head2 warrenty() accessor

The Plugin warrenty can be accessed and changed by using the warrenty()
method.

=over

$plugin->warrenty("MinisculeLimp Inc provides an absolute warrenty of fitness for any purpose ...");

my $warrenty = $plugin->warrenty();

=back

=head2 checker() accessor

The Plugin checker can be accessed and changed by using the checker()
method.

=over

$plugin->checker(\&_check_that_the_universe_has_a_boundary);

my $checker = $plugin->checker();

=back

=head2 action() accessor

The Plugin action can be accessed and changed by using the action()
method.

=over

$plugin->action(\&_DO_THE_REALTHING);

my $action = $plugin->action();

=head2 parameterlists() accessor

The Plugin parameterlists can be accessed and changed by using the parameterlists()
method.

=over

$plugin->parameterlists([ [ fred, jill, adam ], help, version ]);

my $parameterlists = $plugin->parameterlists();

=back

=head2 add_parameterlist

The Plugin parameterlists can be extended by appending a new parameterlist to the
existing list.

=over

$plugin->add_parameterlist($V_opts);

=back

The method takes a reference to an ARRAY of Plugin::Parameter objects
- B<not currently checked>.

=head1 Operations

The Plugin objects provide methods that support the lifecycle of a plugin.
Currently these include B<setup>, B<init>, B<run>, B<final>, and B<stop>.

Only the B<setup> and B<init> functions have useful implementations at
present. Although B<run> will call a subroutine, passing the plugin object
as the only argument. This subroutine can be set, accessed by reference
using the I<action> accessor method.

=head2 setup() operation

The setup method is called to initialise the internal structures prior to use
by the later operations. Setup should always be called before any of the
other functions. Currently it processes the parameterlists producing the
structures used to parse the command options. It has a table of bindings
which determine the low level type of the options being processed. For example
B<HOSTNAME> is a string type option and will be parsed from the command line
as such. Any option type (drawn from the Plugin::Parameter->type() accessor)
that is not in the recognised table will be treated as a string and processed
as such.

The currently recognised list of types and their bindings are:

=over

my %opttypes = ( HOSTNAME     => string,
		 HOSTADDRESS  => string,
		 STRING       => string,
		 INTEGER      => integer,
		 TIMEOUT      => integer,
		 PORT         => string,
	         FILENAME     => string,
	         PATH         => string,
		 SMBPATH      => string,
		 TEMPERATURE  => integer,
		 MINUTES      => integer,
		 SECONDS      => integer,
		 PERCENT      => string,
	         COMMUNITY    => string,
		 THRESHOLD    => string,
		 DIRECTORY    => string,
	         ASNUMBER     => integer,
		 USER         => string,
		 PASSWORD     => string,
	         WORKGROUP    => string,
	         AUTHPROTOCOL => string,
	         PRIVPROTOCOL => string,
	         SNMPVERSION  => string );

=back

Currently this is a compile time array and is used only to bind the
GetOptions arguments. Later implementations will use this information
to determine which checker functions to call by default and whether
additional type checking should be done on parsing.

The setup method, further processes the input parameterlist to define
the bindings for the parameters. A binding is the location or function
that receives the optional data provided with a parameter. If not set
explicitly then setup binds the parameter to a default variable constructed
as B<$main::opt_firstflag> where firstflag is the first flag that introduces
the parameter or the parameter name if no flags are provided.

This binding is a one-off thing and will not be redone if the flags are
changed after setup is called. A reset operation is being considered
but this requires additional cooperation from the parameter object.

=head2 init() Operation

The init method is called to finalise the initialisation of the run time
environment.It uses the Getopt::Long::Getoptions function to process the
command line. This may change if the full parameter list functionality
is implemented. The options are parsed with 'auto-abbrev', 'bundling',
and 'no_ignore_case' set.

A failure in the GetOptions call currently results in an error message
from GetOptions itself followed by a usage statement and an UNKNOWN exit.

If either of the help or version options are available and have been set
then the B<help()> and b<version()> methods for the plugin are called and
an OK exit is issued.

If none of the inbuilt operations are activated then B<init()> processes
the parameterlists to binding remaining options to the parameters. This
allows positional variables as well as flag based variables.

The logic is currently as follows:

=over

Each parameterlist is processed in order.

Each parameter within the list is processed in order.

If the bound variable is a SCALAR reference, then if the variable is not
defined the next option is assigned to the bound SCALAR.

If the bound variable is an ARRAY reference then the next option is pushed
onto the end of the ARRAY. This happens regardlise of whether the option the
ARRAY has already received any information -- probably some sort of bug, but
this is waht you get.

If the bound variable is a HASH reference and the entry has not already been
set using the following rules, then the next option is stored in the hash
using the first flag as a key or the parameter name if no flags exist.

If the bound variable is a CODE reference then the procedure is called with
the next option as its sole argument - consideration is being given to passing
the parameter and plugin object as subsequent arguments.

Where there are not more arguments to be processed, and the bound variable has
not been set already, then if the default value is set for the parameter this
will be assigned/passed to the bound variable. That is a SCALAR reference will
be assigned the parameter default; a HASH reference will have the parameter
default assigned to the key of the first flag or the parameter name; a CODE
reference will be passed the default as its first argument.

=back

As command line arguments have been assigned to all parameters, then the
checker for each parameter is activated. A checker method has 3 arguments
the reference to the bound option, the parameter object and the plugin object.

A checker is expected to report any errors found, currently this is done
via the utils::usage interface. However, an internal problem reporting
scheme is under consideration, and the checkers are expected to return
failure information via an undef result in Scalar context, an dundef with error
information in List context. Exception/Error handling is considered below.

Once all parameters are processed the B<init()> operation will check to see if
any unprocessed command line arguments remain. If so a usage report is produced
and the error is output with a UNKNOWN status.

Finally, B<init()> will activate the plugin level checker. The result from
init is currently undef, but this may change shortly to allow failure to be
signalled from the result.

=head2 run() operation

Run is called after init and currently does no more than execute the action
operation bound into the plugin.

=head2 final() operation

A no-op in the current implementation

=head2 stop() operation

A no-op in the current implementation

=head1 Timeout management

The plugin object supports a timeout management facility whereby plugin
operations can be surrounded by start and stop timeout functions.

The start/stop pair enable the trapping of timeout failures, not already
trapped elsewhere. The trap reports an UNKNOWN result and error message.

=over

$plugin->start_timeout($timeout, "We went pop because time ran out");

do lots.......

$plugin->stop_timeout();

=back

=head1 Usage, Help, Version ...

The plugin object provides methods to report a standard format usage
statement - generated from the parameterlists. A more detailed help
statement is also avaialable as is the ability to report plugin version.
These are supported by the methods:

=over

$plugin->usage();

$plugin->help();

$plugin->version();


=back

Each of these methods tests the --xml flag and if set will output the
information as XML which can be parsed to set up automatic learning features
for other user interfaces.

=head1 Further Information

The Plugin module is a work in progress. It has been integrated with the plugins
available in nagios-plugins-1.4.0alpha1 CVS head as of the 20th March 2004.

Further work will make plugin writing much more about the gathering and
processing of information and less about producing correct output from
properly formatted arguments.

=head1 AUTHOR

Howard E. Wilkinson <howard@cohtech.com>

=head1 Copyright

Copyright (C) 2004 Howard Wilkinson. All rights reserved. This program
is free software; you may redistribute it and/or modify it under the
same terms as Perl itself.

=cut

