#!/usr/bin/perl
#
# monitor host reboots via SNMP, asynchronously
#
# for use with "mon"
#
# options:
#
#   asynch-reboot.monitor --statefile=filename --dir=dir host1 host2...
#
#   Since this is scheduled from mon, it must maintain state between
#   runs. It uses the "statefile" for this, in which it stores a
#   sysUpTime sample for each host specified on the command line.
#
#   THIS STATE FILE MUST NOT BE SHARED BETWEEN MULTIPLE INSTANCES OF THIS
#   MONITOR, SINCE IT DOES NOT HANDLE LOCKING OF THE FILE DURING UPDATES!!
#
# Jim Trocki
#
# $Id: asyncreboot.monitor,v 1.1.1.1 2004/06/09 05:18:05 trockij Exp $
#
#    Copyright (C) 1998, Jim Trocki
#
#    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
#
use SNMP 1.8;
use Getopt::Long;

sub secs_to_hms;
sub read_state_file;
sub write_state_file;
sub secs_to_hms;
sub print_failures;

GetOptions (\%opt, "statefile=s", "dir=s", "timeout=i");

$STATEDIR = $opt{"dir"} || "/usr/lib/mon/state.d";
$STATEFILE = $opt{"statefile"} || "state";
$STATE = "$STATEDIR/$STATEFILE";
$TIMEOUT = $opt{"timeout"} || 10;

die "reboot state dir $STATEDIR does not exist\n"
    if (! -w $STATEDIR);

die "no host arguments\n" if (@ARGV == 0);

read_state_file;

%getting  = ();
%gotten   = ();
%sessions = ();

foreach my $host (@ARGV) {
    $sessions{$host} = new SNMP::Session (
    	"DestHost" => $host,
	"Retries" => 10,
	"Version" => 2,
    );

    $getting{$host} = 1;

    $sessions{$host}->get (
    	[['.1.3.6.1.2.1.1.3.0']],
	[\&h, $sessions{$host}],
    );
}

SNMP::MainLoop($TIMEOUT);

#
# what was gotten and what was not
#
@failures = ();
foreach my $host (keys %gotten) {

    next if ($gotten{$host}->{"uptime"} eq "timeout");

    #
    # no history for this
    #
    if (!defined $last_sample{$host}{"uptime"}) {

    } elsif ($last_sample{$host}{"uptime"} < 2**32 - (120 * 60 * 100) &&
	    $gotten{$host}->{"uptime"} < $last_sample{$host}{"uptime"}) {
    	push (@failures, $host);
	$last_sample{$host}{"olduptime"} = $last_sample{$host}{"uptime"};
	$last_sample{$host}{"oldcheck"} = $last_sample{$host}{"lastcheck"};
    }

    $last_sample{$host}{"uptime"} = $gotten{$host}->{"uptime"};
    $last_sample{$host}{"lastcheck"} = $gotten{$host}->{"time"};
}

for (keys %getting) {
    print "notgot $_ $getting{$_}\n";
    $gotten{$_}->{"uptime"} = "timeout";
    $gotten{$_}->{"time"} = time;
    $gotten{$_}->{"error"} = "timeout";
}

write_state_file;

#
# all is OK, nobody has rebooted
#
if (@failures == 0) {
    exit;
}

print_failures;

exit 1;



#
# callback for asynch requests
#
sub h {
    $gotten{$_[0]->{"DestHost"}}->{"time"} = time;

    delete $getting{$_[0]->{"DestHost"}};

    if (!defined ($_[1])) {
	$gotten{$_[0]->{"DestHost"}}->{"uptime"} = "timeout";
	$gotten{$_[0]->{"DestHost"}}->{"error"} = $_[0]->{"ErrorStr"};
	return;
    }

    $gotten{$_[0]->{"DestHost"}}->{"uptime"} = $_[1][0]->val;

}


sub secs_to_hms {
    my ($s) = @_;
    my ($dd, $hh, $mm, $ss);

    $dd = int ($s / 86400);
    $s -= $dd * 86400;

    $hh = int ($s / 3600);
    $s -= $hh * 3600;

    $mm = int ($s / 60);
    $s -= $mm * 60;

    $ss = $s;

    if ($dd == 0) {
        sprintf("%02d:%02d", $hh, $mm);

    } else {
        sprintf("%d days, %02d:%02d", $dd, $hh, $mm);
    }
}


sub read_state_file {
    my $host;

    if (! -f $STATE) {
	open (O, ">$STATE");
	close (O);
    }

    #
    # read in state file
    #
    if (!open (IN, "$STATE")) {
	die "could not open state file $STATE\n";
    }

    while (defined ($host = <IN>)) {
	if ($host =~ /^(\S+) (\d+) (\d+)/) {
	    $last_sample{$1}{"uptime"} = $2;
	    $last_sample{$1}{"lastcheck"} = $3;
	}
    }

    close (IN);
}


sub write_state_file {
    #
    # update state file
    #
    if (!open (OUT, ">$STATE")) {
	die "could not open $STATEFILE for writing: $!\n";
    }

    foreach my $k (sort keys %last_sample) {
	print OUT "$k $last_sample{$k}{uptime} $last_sample{$k}{lastcheck}\n";
    }

    close (OUT);
}


#
# we have reboots, so calculate uptime, downtime,
# and report it
#
sub print_failures {
    my $t = time;
    my @f;

    foreach my $host (@failures) {
	my $downtime = secs_to_hms (
	    $t - $last_sample{$host}{"oldcheck"} -
	    int ($last_sample{$host}{"uptime"} / 100)
	);

	my $uptime = secs_to_hms (
	    int ($last_sample{$host}{"olduptime"} / 100)
	);

	push (@f, "$host / down $downtime / up $uptime");
    }

    @f = sort @f;
    print join (" ", sort @failures), "\n";

    printf ("%-20s  %s\n", "host", "rebooted on");
    printf ("----------------------------------------------\n");
    my $timen = time;

    foreach my $host (@failures) {
	my $secs = int($last_sample{$host}{"uptime"} / 100);
	my $t = localtime ($timen - $secs);
	printf ("%-20s  %s\n", $host, $t);
    }

    print "\n";
    print join ("\n", @f), "\n";
}
