#!/usr/bin/perl -w
# -*- perl -*-

=head1 NAME

apt_all - Plugin to monitor packages that should be installed on
systems using apt-get (mostly Debian, but also RedHat).

=head1 NOTES

The differences between this plugin and the apt plugins, is that this
plugin separates each distro with its own plot, and actually draws
graphs.

=head1 CONFIGURATION

No configuration needed

=head1 MAGIC MARKERS

 #%# family=manual
 #%# capabilities=autoconf

=cut

# Now for the real work...

use strict;
use File::stat;

$ENV{'LANG'}="C";
$ENV{'LC_ALL'}="C";

# APT cache directory
my $aptcache = '/var/cache/apt';
my $dpkgstatus = '/var/lib/dpkg/status';
# Releases to monitor
my @releases = ("stable", "testing", "unstable");

# Print the apt state, regenerating the state cache if necessary
sub print_state() {
    my $statefile = $ENV{MUNIN_PLUGSTATE} . "/plugin-apt.state";
    if (-l $statefile) {
	die("$statefile is a symbolic link, refusing to read it.");
    }

    if (is_out_of_date($statefile)) {
        update_state ($statefile);
    }

    if (! -e "$statefile") {
	die ("$statefile does not exist. Something wicked happened.");
    }

    open(STATE, "$statefile")
	or die("Couldn't open state file $statefile for reading.");
    print while <STATE>;
    close STATE;
}

# Checks if the state file is out of date relative to the apt cache
# or if the state file is missing.
# If the apt cache isn't found, dies with a hint
sub is_out_of_date {
    my ($statefile) = @_;

    my $apttime = get_last_apt_update();
    if ($apttime == 0) {
        die("Unable to determine last apt update from '$aptcache'. "
            . "Maybe you want to run 'apt-get update' as root to populate the cache?");
    }

    if (! -e "$statefile") { return 1; }
    my $statetime = stat($statefile)->mtime;

    return $apttime >= $statetime;
}

# Gets the most recent update time of the apt package caches
sub get_last_apt_update {
    my $apttime = 0;
    if (opendir(DIR, $aptcache)) {
        for my $aptfile (grep { !/^\./ && /pkgcache\.bin$/ } readdir(DIR)) {
            my $filetime = stat("${aptcache}/${aptfile}")->mtime;
            if ($filetime > $apttime) { $apttime = $filetime; }
        }
        closedir(DIR);
    }

    if ($apttime > 0 && -f $dpkgstatus) {
        my $filetime = stat($dpkgstatus)->mtime;
        if ($filetime > $apttime) { $apttime = $filetime; }
    }

    return $apttime;
}

# Recreate the state cache
sub update_state() {
	my ($statefile) = @_;
	if (-l $statefile) {
		die("$statefile is a symbolic link, refusing to touch it.");
	}
	open(STATE, ">$statefile")
		or die("Couldn't open state file $statefile for writing.");
	foreach my $release (@releases) {
	    my $apt="apt-get -u dist-upgrade --print-uris --yes -t $release |";
	    open (APT, "$apt") or exit 22;

	    my @pending = ();
	    my $hold    = 0;
	    my @remove  = ();
	    my @install = ();

	    while (<APT>)
	    {
		    if (/^The following packages will be REMOVED:/)
		    {
			    my $where = 0;
			    while (<APT>)
			    {
				    last if (/^\S/);
				    foreach my $package (split /\s+/)
				    {
					    next unless ($package =~ /\S/);
					    push (@remove, "-$package");
				    }
			    }
		    }
		    if (/^The following NEW packages will be installed:/)
		    {
			    my $where = 0;
			    while (<APT>)
			    {
				    last if (/^\S/);
				    foreach my $package (split /\s+/)
				    {
					    next unless ($package =~ /\S/);
					    push (@install, "+$package");
				    }
			    }
		    }
		    if (/^The following packages will be upgraded/)
		    {
			    my $where = 0;
			    while (<APT>)
			    {
				    last if (/^\S/);
				    foreach my $package (split /\s+/)
				    {
					    next unless ($package =~ /\S/);
					    push (@pending, $package);
				    }
			    }
		    }
		    if (/^\d+\supgraded,\s\d+\snewly installed, \d+ to remove and (\d+) not upgraded/)
		    {
			    $hold = $1;
		    }
	    }

	    push (@pending, @install) if @install;
	    push (@pending, @remove ) if @remove;
	    close APT;

	    print STATE "pending_$release.value ", scalar (@pending), "\n";
	    if (@pending)
	    {
		    print STATE "pending_$release.extinfo ", join (' ', @pending), "\n";
	    }
	    print STATE "hold_$release.value $hold\n";

	}
	close(STATE);
}

if ($ARGV[0] and $ARGV[0] eq "autoconf")
{
	`apt-get -v >/dev/null 2>/dev/null`;
	if ($? eq "0")
	{
		print "yes\n";
		exit 0;
	}
	else
	{
		print "no (apt-get not found)\n";
		exit 0;
	}
}

if ($ARGV[0] and $ARGV[0] eq "config") {

    print "graph_title Pending packages\n";
    print "graph_vlabel Total packages\n";
    print "graph_category system\n";

    foreach my $release (@releases) {
	print "pending_$release.label pending_$release\n";
	print "pending_$release.warning 0:0\n";
	print "hold_$release.label hold_$release\n";
    }
    exit 0;
}

print_state ();

exit 0;

# vim:syntax=perl
