#!/usr/bin/perl
#  Copyright (C) 2002  Stanislav Sinyagin
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.

# $Id: devdiscover.in,v 1.10 2006/01/05 16:19:53 ssinyagin Exp $
# Stanislav Sinyagin <ssinyagin@yahoo.com>

# Collect the router information and create the XML file

BEGIN { require '/usr/share/torrus/conf_defaults/devdiscover-config.pl'; }

use strict;
use Getopt::Long;
use XML::LibXML;

use Torrus::Log;
use Torrus::DevDiscover;
use Torrus::ConfigBuilder;

our $infile;
our $makedirs;
our $limitre;
our $forcebundle;
our $fallback;

# Hidden parameter for debugging
our $snmpdebug = 0;
our $debug = 0;
our $verbose = 0;

my %formatsSupported = ( '1.0' => 1 );


my $creator = "Torrus version 1.0.4\n" .
    "This file was generated by command:\n" .
    $0 . " \\\n";
foreach my $arg ( @ARGV )
{
    if( $arg =~ /^--/ )
    {
        $creator .= ' ' . $arg . ' ';
    }
    else
    {
        $creator .= "\'" . $arg . "\'\\\n";
    }
}
$creator .= "\n On " . scalar(localtime(time));

my $ok = GetOptions(
                    'in=s'        => \$infile,
                    'mkdir'       => \$makedirs,
                    'limit=s'     => \$limitre,
                    'forcebundle' => \$forcebundle,
                    'fallback=i'  => \$fallback,
                    'snmpdebug'   => \$snmpdebug,
                    'verbose'     => \$verbose,
                    'debug'       => \$debug
                    );

if( not $ok or not $infile or scalar( @ARGV ) > 0 )
{
    print STDERR "Usage: $0 --in=filename.ddx options...\n",
    "Options:\n",
    " --in=filename.ddx       discovery instructions XML file\n",
    " --mkdir                 create data-dir directories\n",
    " --limit=regexp          limit the discovery by output files\n",
    " --forcebundle           always write the bundle file\n",
    " --fallback=integer      maximum age of XML file to fall back to\n",
    " --verbose               print extra information\n",
    " --debug                 print debugging information\n",
    " --snmpdebug             print SNMP protocol details\n",
    "\n",
    "Input file may be generated by genddx utility\n";

    exit 1;
}

if( $snmpdebug )
{
    $Net::SNMP::Transport::UDP::DEBUG = 1;
    $Net::SNMP::Message::DEBUG = 1;
    $Net::SNMP::MessageProcessing::DEBUG = 1;
    $Net::SNMP::Dispatcher::DEBUG = 1;
}

if( $debug )
{
    Torrus::Log::setLevel('debug');
}
elsif( $verbose )
{
    Torrus::Log::setLevel('verbose');
}

if( not -r $infile )
{
    my $altfile = $Torrus::Global::discoveryDir . $infile;
    if( not -r $altfile )
    {
        Error('Cannot find file ' . $infile .
              ' neither in current directory nor in ' .
              $Torrus::Global::discoveryDir);
        exit 1;
    }
    else
    {
        $infile = $altfile;
    }
}

Verbose('Processing ' . $infile);

my $parser = new XML::LibXML;
my $doc;
eval { $doc = $parser->parse_file( $infile );  };
if( $@ )
{
    Error("Failed to parse $infile: $@");
    exit 1;
}

my $root = $doc->documentElement();
if( $root->nodeName() ne 'snmp-discovery' )
{
    Error('XML root element is not "snmp-discovery" in ' . $infile);
    exit 1;
}

{
    my $format_version =
        (($root->getElementsByTagName('file-info'))[0]->
         getElementsByTagName('format-version'))[0]->textContent();

    $format_version =~ s/\s//g;

    if( not $format_version or not $formatsSupported{$format_version} )
    {
        Error('Invalid format or format version not supported: ' . $infile);
        exit 1;
    }
}


my $everythingsOk = 1;

my $globalParams = parseParams( $root );

my $perOutfileHostParams = {};
my %outputBundles;

foreach my $hostNode ( $root->getChildrenByTagName('host') )
{
    my $hostParams = parseParams( $hostNode, $globalParams );
    normalizeParams( $hostParams );

    my $outfile = $hostParams->{'output-file'};
    if( not exists($perOutfileHostParams->{$outfile}) )
    {
        $perOutfileHostParams->{$outfile} = [];
    }
    push( @{$perOutfileHostParams->{$outfile}}, $hostParams );

    my $outBundles = $hostParams->{'output-bundle'};
    if( length( $outBundles ) > 0 )
    {
        foreach my $bundleName ( split( /\s*,\s*/, $outBundles ) )
        {
            $bundleName = absXmlFilename( $bundleName );
            $outputBundles{$bundleName}{ relXmlFilename($outfile) } = 1;
        }
    }
}

# Cleanup memory
undef $root;
undef $doc;
undef $parser;


foreach my $outfile ( sort keys %{$perOutfileHostParams} )
{
    if( defined( $limitre ) )
    {
        my $filename = $outfile;
        $filename =~ s/^.*\///;

        if( $filename !~ $limitre )
        {
            next;
        }
    }
        
    Verbose('Preparing to write ' . $outfile);

    my $dd = new Torrus::DevDiscover;

    my $ok = 1;

    foreach my $hostParams ( @{$perOutfileHostParams->{$outfile}} )
    {
        $ok = $dd->discover( $hostParams );

        if( not $ok )
        {
            Error($outfile . ' was not written because of errors');
            $everythingsOk = 0;
            last;
        }
    }

    if( $ok )
    {
        my $cb = new Torrus::ConfigBuilder;

        $cb->addCreatorInfo( $creator );

        $dd->buildConfig( $cb );
        $cb->addRequiredFiles();
        $cb->addStatistics();

        $ok = $cb->toFile( $outfile );
        if( $ok )
        {
            Verbose('Wrote ' . $outfile);
        }
        else
        {
            Error('Cannot write ' . $outfile . ': ' . $!);
            $everythingsOk = 0;
        }
    }
    else
    {
        my $removeFromBundle = 1;
        
        if( $forcebundle )
        {
            if( defined( $fallback ) and
                -e $outfile and -M $outfile <= $fallback )
            {               
                Warn('Falling back to the old version of ' .
                     relXmlFilename($outfile));
                $removeFromBundle = 0;
            }
            $everythingsOk = 1;
        }

        if( $removeFromBundle )
        {
            foreach my $bundleName ( sort keys %outputBundles )
            {
                delete $outputBundles{$bundleName}{ relXmlFilename($outfile) };
                Warn('Bundle ' . $bundleName . ' will not have ' .
                     relXmlFilename($outfile) . ' included because of errors');
            }
        }
    }

    if( $makedirs )
    {
        if( $everythingsOk )
        {
            my ($login,$pass,$uid,$gid) = getpwnam('torrus')
                or die "Cannot get user details for torrus";

            foreach my $dir ( $dd->listDataDirs() )
            {
                if( not -d $dir )
                {
                    Debug('Creating directory: ' . $dir);
                    mkdir( $dir ) or
                        die('Cannot create directory: ' . $dir . ': ' . $!);
                    chown( $uid, $gid, $dir ) or
                        die('Cannot change ownership for ' . $dir . ': ' . $!);
                    chmod( 02755, $dir ) or
                        die('Cannot chmod 02755 for ' . $dir . ': ' . $!);
                }
            }
        }
        else
        {
            Error('Skipping mkdir because of errors');
        }
    }
}


if( scalar( keys %outputBundles ) > 0 )
{
    if( defined( $limitre ) )
    {
        Warn('Cannot write bundles with --limit option specified. ' .
             'Bundle files remain unchanged');
    }
    elsif( $everythingsOk )
    {
        foreach my $bundleName ( sort keys %outputBundles )
        {
            my $cb = new Torrus::ConfigBuilder;

            $cb->addCreatorInfo( $creator );
            
            foreach my $bundleMember
                ( sort keys %{$outputBundles{$bundleName}} )
            {
                $cb->addFileInclusion( $bundleMember );
            }

            my $ok = $cb->toFile( $bundleName );
            if( $ok )
            {
                Verbose('Wrote bundle to ' . $bundleName);
            }
            else
            {
                Error('Cannot write bundle to ' . $bundleName . ': ' . $!);
                $everythingsOk = 0;
            }
        }
    }
    else
    {
        Error('Skipping bundles generation because of errors');
    }
}


exit($everythingsOk ? 0:1);


sub parseParams
{
    my $parentNode = shift;
    my $paramhash = shift;

    # Clone the parameters hash
    my $ret = {};
    if( $paramhash )
    {
        while( my($key, $val) = each %{$paramhash} )
        {
            $ret->{$key} = $val;
        }
    }

    foreach my $paramNode ( $parentNode->getChildrenByTagName('param') )
    {
        my $param = $paramNode->getAttribute('name');
        my $value = $paramNode->getAttribute('value');

        if( not $param )
        {
            Error("Parameter without name");
            exit 1;
        }

        if( not defined( $value ) )
        {
            $value = $paramNode->textContent();
        }

        # Remove spaces in the head and tail.
        $value =~ s/^\s+//;
        $value =~ s/\s+$//;

        $ret->{$param} = $value;
    }
    return $ret;
}


sub normalizeParams
{
    my $params = shift;

    if( not defined( $params->{'output-file'} ) )
    {
        Warn('output-file parameter is not defined. Using routers.xml');
        $params->{'output-file'} = 'routers.xml';
    }
    else
    {
        $params->{'output-file'} = absXmlFilename( $params->{'output-file'} );
    }

    if( defined( $params->{'host-subtree'} ) )
    {
        my $subtree = $params->{'host-subtree'};

        if( $subtree !~ /^\/[0-9A-Za-z_\-\.\/]*$/ or
            $subtree =~ /\.\./ )
        {
            Error("Invalid format for subtree name: " . $subtree);
            exit 1;
        }
    }

    if( defined( $params->{'snmp-community'} ) )
    {
        # Remove any possible Unicode character treatment
        $params->{'snmp-community'} =
            pack( 'A*', $params->{'snmp-community'} );
    }
}


# Replaces $XMLCONFIG with the XML root directory
sub absXmlFilename
{
    my $filename = shift;

    my $subst = '$XMLCONFIG';
    my $offset = index( $filename, $subst );
    if( $offset >= 0 )
    {
        my $len = length( $subst );
        substr( $filename, $offset, $len ) = $Torrus::Global::siteXmlDir;
    }
    else
    {
        if( $filename !~ /^\// )
        {
            $filename = $Torrus::Global::siteXmlDir . '/' . $filename;
        }
    }
    return $filename;
}


# Removes XML root directory from path
sub relXmlFilename
{
    my $filename = shift;

    my $subst = $Torrus::Global::siteXmlDir;
    my $len = length( $subst );

    if( $filename =~ /^\// )
    {
        my $offset = index( $filename, $subst );
        if( $offset == 0 )
        {
            $filename = substr( $filename, $len );
            # we don't know if xmldir has a trailing slash
            $filename =~ s/^\///;
        }
    }
    return $filename;
}


# Local Variables:
# mode: perl
# indent-tabs-mode: nil
# perl-indent-level: 4
# End:
