# -*- perl -*-

# Cricket: a configuration, polling and data display wrapper for RRD files
#
#    Copyright (C) 1998 Jeff R. Allen and WebTV Networks, 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; 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., 675 Mass Ave, Cambridge, MA 02139, USA.

package Common::Map;

use snmpUtils;
use Common::Log;
use Common::Util;

# This sets up the target right to sneak through the instance
# evaluation code... we do the actual mapping after the target
# gets expanded.

sub mapPrepareInstance {
    my($target) = @_;
    my($inst) = $target->{'inst'};

    return unless defined($inst);

    Debug("Preparing $inst");
    my($mapkey) = ($inst =~ /^map\((.*)\)$/);
    return unless defined($mapkey);

    # ok, if we got this far, we have a mapkey, so put it into
    # the target dict for later, and fake the inst into an
    # eval-able thing so that we can squeak through the instance
    # eval code.

    $target->{'--mapkey--'} = lc($mapkey);
    $target->{'inst'} = "0";

    return;
}

sub mapInstance {
    my($name, $target) = @_;

    # Don't try to map it if we should be touching it:
    return if defined($target->{'targets'});
    return if defined($target->{'mtargets'});
    if ($Common::global::isCollector) {
        # we only honor collect=false in the collector, since
        # we still need to map it in the grapher so that we
        # have the right instance number on hand in the grapher.
        return if (defined($target->{'collect'}) &&
                   isFalse($target->{'collect'}));
    }

    my($mapkey) = $target->{'--mapkey--'};
    # there's no work to do, so leave.
    return unless (defined($mapkey));

    # if there's no file for some reason, all this fails
    # correctly -- i.e. no meta data is returned.
    my($rrd, $file);
    $file = $target->{'rrd-datafile'};
    $rrd = new RRD::File ( -file => $file );

    # we only want to check the cache if we are coming
    # through here for the first time... see retrieveData
    # the the "second coming" of mapInstance.

    my($metaRef) = $rrd->getMeta();
    if (defined($target->{'--verify-mapkey--'})) {
        # Lose the cached last-inst to force the SNMP walk,
        # since evidently the cached one didn't work.
        delete($metaRef->{'last-inst'});
    }

    # if cached, try to use it, but warn the retriever it needs to
    # verify the inst. if not cached, do a lookup.

    my($cachedInst) = $metaRef->{'last-inst'};
    if (defined($cachedInst)) {
        $target->{'inst'} = $cachedInst;
        $target->{'--verify-mapkey--'} = $mapkey;
    } else {
        my($inst) = mapLookup($name, $target);
        if (! defined($inst)) {
            # set the inst key to null so that our target ends up
            # unresolveable -- i.e. the OID will come out useless
            $target->{'inst'} = '';

            Warn("Failed to map instance for " .
                 $target->{'auto-target-name'} .
                 ". Instance is now set to nothing.");
        } else {
            $target->{'inst'} = $inst;

            # save it for next time
            $metaRef->{'last-inst'} = $inst;
            $rrd->setMeta($metaRef);
        }
    }
}

# lookup does the hard work. mapkey tells us which map entry
# to use. then we use the ds and match tags in that entry
# to find the instance number

sub mapLookup {
    my($name, $target) = @_;

    my($mapkey) = $target->{'--mapkey--'};
    return unless defined($mapkey);

    my($mapRef) = $Common::global::gCT->configHash($name,
                                                   'map', $mapkey, $target);
    my($match) = $mapRef->{'match'};
    if (! defined($match)) {
        Warn("No match tag found in map entry $mapkey");
        return;
    }
    $match = ConfigTree::Cache::expandString($match, $target, \&Warn);

    # this is expected to hold a string like this: comm@host:port
    my($snmp) = $target->{'snmp'};

    if (! defined($main::gMapCache{$snmp}->{$mapkey})) {
        # cache does not exist, so try to load it
        my($baseOID) = $mapRef->{'base-oid'};

        my($oidMap) = $Common::global::gCT->configHash($name, 'oid');
        my($oid) = Common::Util::mapOid($oidMap, $baseOID);

        if (! defined($oid)) {
            Warn("Missing base-oid in $mapkey map entry.");
            return;
        }

        my($hp) = $snmp;
        $hp =~ s/^.*@//;        # remove the community string, if it's there
        # (This keeps it out of the logfiles, which seems like
        # a good idea.)

        Info("Walking $baseOID for $hp to resolve $mapkey mapping");
        my(@ret) = snmpUtils::walk($snmp, $oid);

        my($row);
        foreach $row (@ret) {
            my($inst, $name) = split(':', $row, 2);
            $main::gMapCache{$snmp}->{$mapkey}->{$name} = $inst;
        }
    }

    # find the inst number -- either by using a regexp match,
    # or by a simple table lookup.

    # does it look like a regexp? (i.e. "/^foo$/") (we allow
    # whitespace, in case they are incompetent with the quote key...
    if ($match =~ /^\s*\/(.*)\/\s*$/) {
        # this is a regexp
        $match = $1;

        Debug("Regexp is /$match/i");

        # this resets the iterator, so the each will get everything
        scalar keys(%{$main::gMapCache{$snmp}->{$mapkey}});

        my($name, $inst);
        while (($name, $inst) = each(%{$main::gMapCache{$snmp}->{$mapkey}})) {
            Debug("  checking: $name");
            if ($name =~ /$match/i) {
                return $inst;
            }
        }
        # didn't match anything... return nothing.
        return;
    } else {
        Debug("Attempting lookup on $match.");
        return $main::gMapCache{$snmp}->{$mapkey}->{$match};
    }
}

1;

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