#!/usr/bin/perl -w
#
# Module: show_version
#
# **** License ****
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation.
#
# 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.
#
# This code was originally developed by Vyatta, Inc.
# Portions created by Vyatta are Copyright (C) 2005, 2006, 2007 Vyatta, Inc.
# All Rights Reserved.
#
# Author: Rick Balocca
# Date: 2007
# Description:
#
# **** End License ****
#
use strict;
use warnings;

#
# Global hash of debians in the base install and now.
#
my $rHoH_base_debs;
my $rHoH_now_debs;

my $base          = '/opt/vyatta/etc';
my $versionfile   = "$base/version";
my $buildfile     = "$base/build.txt";
my $debsfile      = "$base/deb-versions.txt";

sub echo_file {
    my ($file) = @_;

    my @lines = ();
    if (!(-e $file)) {
	return @lines;
    }

    open(my $FH, '<', $file) or die "Unable to open [$file]\n";
    @lines=<$FH>;
    close($FH);
    return @lines;
}

# This follows the chain from /boot/grub/menu.cfg which
# boots /boot/vmlinuz to find the version of kernel running
sub get_image_type {
    my $kernel = readlink('/boot/vmlinuz');
    my $version;

    unless (defined($kernel)) {
	warn "Can not read link /boot/vmlinuz: $!\n";
	return;
    }

    unless ($kernel =~ /^vmlinuz-.*-([^-]*)-vyatta(.*)$/) {
	warn "Unknown kernel version: $kernel\n";
	return;
    }

    if ($1 eq '586') {
	$version = "Intel 32bit";
    } elsif ($1 eq "amd64") {
	$version = "Intel 64bit";
    } else {
	$version = $1;
    }

    if ($2 eq '-virt') {
	$version .= " Virtual"
    }

    return $version;
}

#
# convert the "dpkg -l" output have same format as deb-versions.txt
#
sub get_pkg_version {
    my @lines = @_;

    my @new_lines = ();
    foreach my $line (@lines) {
	if ($line =~ /^[D\|\+]/) {
	    next;   # skip header
	}
	my ($status, $pkg, $version) = split(/[ \t\n]+/, $line, 4);
	if ($status =~ /^i/) {
	    push(@new_lines, "$pkg $version");
	}
    }
    return @new_lines;
}

sub read_pkg_file {
    my @pkgs_list = @_;

    my %HoH = ();
    my ($name, $version);
    foreach my $line (@pkgs_list) {
	($name, $version) = split(/[ \t\n]+/, $line, 3);
	$HoH{$name}{'version'} = $version;
    }
    return \%HoH;
}

sub show_added {
    for my $name (sort keys %$rHoH_now_debs) {
	if (!$rHoH_base_debs->{$name}) {
	    printf("Aii %-25s %-25s\n",
		   $name, $rHoH_now_debs->{$name}->{'version'});
	}
    }
}

sub show_deleted {
    for my $name (sort keys %$rHoH_base_debs) {
	if (!$rHoH_now_debs->{$name}) {
	    printf("X   %-25s %-25s\n",
		   $name, $rHoH_base_debs->{$name}->{'version'});
	}
    }
}

sub show_upgraded_downgraded {
    my ($up_down) = @_;

    my ($symbol, $op, $ver_base, $ver_now, $cmd);
    if ($up_down eq "upgraded") {
	$symbol = "U";
	$op = "lt";
    } else {
	$symbol = "D";
	$op = "gt";
    }
    for my $name (sort keys %$rHoH_base_debs) {
	if ($rHoH_now_debs->{$name}) {
	    $ver_base = $rHoH_base_debs->{$name}{'version'};
	    $ver_now  =  $rHoH_now_debs->{$name}{'version'};
	    if ($ver_base ne $ver_now) {
		$cmd = "dpkg --compare-versions \"$ver_base\" $op \"$ver_now\"";
		if (!system($cmd)) {
		    printf("%sii %-25s %-20s (baseline: %s)\n",
			   $symbol, $name, $ver_now, $ver_base);
		}
            }
	}
    }
}

sub show_upgraded {
    show_upgraded_downgraded("upgraded");
}

sub show_downgraded {
    show_upgraded_downgraded("downgraded");
}

sub show_all {
    show_added();
    show_deleted();
    show_upgraded();
    show_downgraded();
}

my %options = (
    "added"      => \&show_added,
    "deleted",   => \&show_deleted,
    "upgraded"   => \&show_upgraded,
    "downgraded" => \&show_downgraded,
    "all"        => \&show_all,
);

#
# main
#
print(&echo_file($versionfile));
print(&echo_file($buildfile));

my $type = get_image_type();
if ($type) {
    print "System type:  $type\n";
}

my $booted = `grep -e '^unionfs.*/filesystem.squashfs' -e '^aufs.*/filesystem.squashfs' /proc/mounts`;
if (defined($booted) && $booted ne "") {
    $booted="livecd";
} else {
    my $image_boot = `grep -e '^unionfs / unionfs.*squashfs=ro' /proc/mounts`;
    if ($image_boot ne "") {
	$booted="image";
    } else {
	$booted="disk";
    }
}
print "Boot via:     $booted\n";

my $hv = `/opt/vyatta/sbin/hypervisor_vendor`;
if (defined($hv) && $hv ne "") {
    chomp $hv;
    print "Hypervisor:   $hv\n";
}


my $plat_model = `sudo /usr/sbin/dmidecode -s system-product-name`;
chomp $plat_model;
my $plat_sn = `sudo /usr/sbin/dmidecode -s system-serial-number`;
chomp $plat_sn;
my $plat_uuid = `sudo /usr/sbin/dmidecode -s system-uuid`;
chomp $plat_uuid;

if (defined $plat_model && $plat_model ne "" && $plat_model ne " ") {
    print "HW model:     $plat_model\n"
}

if (defined $plat_sn && $plat_sn ne "" && $plat_sn ne " ") {
    print "HW S/N:       $plat_sn\n"
}

if (defined $plat_uuid && $plat_uuid ne "" && $plat_uuid ne " ") {
    print "HW UUID:      $plat_uuid\n"
}

my $uptime = `uptime`;
if (defined $uptime && $uptime ne "") {
    print "Uptime:      $uptime\n";
}

if (!(-e $debsfile)) {
    exit 0;
}
print "\n";
$rHoH_base_debs = read_pkg_file(&echo_file($debsfile));
$rHoH_now_debs  = read_pkg_file(get_pkg_version(`dpkg -l 2> /dev/null`));

if ($#ARGV == 0) {
    if ($options{$ARGV[0]}) {
        $options{$ARGV[0]}->();
    } else {
	print "Usage: showversion [added|deleted|upgraded|downgraded|all]\n";
	exit 1;
    }
}