#!/usr/bin/perl -w

=head1 NAME

xen-update-image - Update the software installed upon offline Xen images.

=head1 SYNOPSIS

  xen-update-image [options] imageName1 imageName2 .. imageNameN

  Help Options:
   --help      Show this scripts help information.
   --manual    Read this scripts manual.
   --version   Show the version number and exit.

  General Options:
   --dir       Specify the directory which contains the image(s).
   --lvm       Specify the LVM volume group which contains the image(s).

=cut



=head1 OPTIONS

=over 8

=item B<--dir>
Specify the directory which contains the image(s).

=item B<--help>
Show the script help.

=item B<--manual>
Read the manual.

=item B<--version>
Show the version number and exit.

=item B<--lvm>
Specify the LVM volume group which contains the image(s).

=back

=cut


=head1 DESCRIPTION

  xen-update-image is a simple script which allows you to update
 a Xen image of Debian which has been created with xen-create-image.

  It does this by mounting the image inside a temporary directory
 then running:

      apt-get update

      apt-get upgrade

  B<NOTE> If the image is already running within Xen this will cause corruption
 otherwise it will allow you to update your image without booting it.

=cut


=head1 EXAMPLES

  The following assumes there are two images which are not currently
 running.  The images are called 'test.my.flat', and 'x11.my.flat'.

  Updating both images can be accomplished by executing:

     xen-update-images --dir=/home/xen test.my.flat x11.my.flat

=cut


=head1 AUTHOR


 Steve
 --
 http://www.steve.org.uk/

 $Id: xen-update-image,v 1.9 2006/08/16 09:42:44 steve Exp $

=cut


=head1 LICENSE

Copyright (c) 2005-2006 by Steve Kemp.  All rights reserved.

This module is free software;
you can redistribute it and/or modify it under
the same terms as Perl itself.
The LICENSE file contains the full text of the license.

=cut


use strict;
use English;
use File::Temp qw/ tempdir /;
use Getopt::Long;
use Pod::Usage;


#
#  Configuration options, initially read from the configuration file
# but may be overridden by the command line.
#
#  Command line flags *always* take precedence over the configuration file.
#
my %CONFIG;

#
# Release number.
#
my $RELEASE = '2.3';


#
#  Read configuration file if it exists.
#
if ( -e "/etc/xen-tools/xen-tools.conf" )
{
    readConfigurationFile( "/etc/xen-tools/xen-tools.conf" );
}


#
#  Parse command line arguments, these override the values from the
# configuration file.
#
parseCommandLineArguments();


#
#  Test that our arguments are sane.
#
checkArguments();


#
#  Abort if non-root user.
#
if ( $EFFECTIVE_USER_ID != 0 )
{
    print <<E_O_ROOT;

  This script is not running with root privileges.

  root privileges are required to successfully mount the disk image(s).

E_O_ROOT

    exit;
}



#
#  Loop over the supplied arguments, and attempt to update each image.
#
while( my $name = shift )
{
    updateXenImage( $name );
}


#
#  All done.
#
exit;



=begin doc

  Mount the primary disk image, so that we're ready to update it.

=end doc

=cut

sub updateXenImage
{
    my ( $name ) = ( @_ );

    #
    #  Create a temporary directory, and prepare to mount the
    # image there.
    #
    my $tmp = tempdir( CLEANUP => 1 );
    my $img = '';


    #
    #  If we're dealing with loopback images find the main one,
    # and mount it.
    #
    if ( $CONFIG{'dir'} )
    {
        # The loopback image.
        $img =  $CONFIG{'dir'} . "/domains/" . $name . "/disk.img";

        if ( ! -e $img )
        {
            print "Disk image '$img' for host '$name' not found\n";
            return;
        }
    }
    elsif ( $CONFIG{'lvm'} )
    {
        # The LVM volume
        $img = "/dev/" . $CONFIG{'lvm'} . "/$name-disk";

        # make sure it exists.
        if ( ! -e $img )
        {
            print "Logical volume '$img' for host '$name' not found\n";
            return;
        }
    }
    else
    {
        die "Can't happen?\n";
    }

    #
    #  Mount the image.
    # 
    my $mount_cmd = "mount -t auto -o loop $img $tmp";
    `$mount_cmd`;

    #
    #  Make sure this is a Debian image.
    #
    if ( ( -e $tmp . "/usr/bin/apt-get" ) &&
         ( -e $tmp . "/etc/apt/sources.list" ) )
    {
       #
       #  Now run the update command.
       #
       system( "chroot $tmp /usr/bin/apt-get update" );


       #
       #  Now upgrade
       #
       system( "DEBIAN_FRONTEND=noninteractive  chroot $tmp /usr/bin/apt-get upgrade --yes --force-yes" );
    }
    else
    {
       print "Xen image $name is not a Debian GNU/Linux image.  Skipping\n";
    }


    #
    #  Unmount
    #
    `umount $tmp`;

}


=begin doc

  Read the configuration file specified.

=end doc

=cut

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

    open( FILE, "<", $file ) or die "Cannot read file '$file' - $!";

    my $line       = "";

    while (defined($line = <FILE>) )
    {
        chomp $line;
       if ($line =~ s/\\$//)
       {
           $line .= <FILE>;
           redo unless eof(FILE);
       }

       # Skip lines beginning with comments
       next if ( $line =~ /^([ \t]*)\#/ );

       # Skip blank lines
       next if ( length( $line ) < 1 );

       # Strip trailing comments.
       if ( $line =~ /(.*)\#(.*)/ )
       {
           $line = $1;
       }

       # Find variable settings
       if ( $line =~ /([^=]+)=([^\n]+)/ )
       {
           my $key = $1;
           my $val = $2;

           # Strip leading and trailing whitespace.
           $key =~ s/^\s+//;
           $key =~ s/\s+$//;
           $val =~ s/^\s+//;
           $val =~ s/\s+$//;

           # Store value.
           $CONFIG{ $key } = $val;
       }
    }

    close( FILE );
}




=begin doc

  Parse the arguments specified upon the command line.

=end doc

=cut

sub parseCommandLineArguments
{
    my $HELP       = 0;
    my $MANUAL       = 0;
    my $VERSION       = 0;

    #  Parse options.
    #
    GetOptions(
              "dir=s",      \$CONFIG{'dir'},
              "lvm=s",      \$CONFIG{'lvm'},
              "help",       \$HELP,
              "manual",     \$MANUAL,
              "version",    \$VERSION
             );

    pod2usage(1) if $HELP;
    pod2usage(-verbose => 2 ) if $MANUAL;

    if ( $VERSION )
    {
       my $REVISION      = '$Revision: 1.9 $';

       if ( $REVISION =~ /1.([0-9.]+) / )
       {
           $REVISION = $1;
       }
       print "xen-update-image release $RELEASE - CVS: $REVISION\n";
       exit;

    }
}



=begin doc

  Test that the options we received from the command line, or our
 configuration file, make sense.

=end doc

=cut

sub checkArguments
{

    #
    #  Make sure we have either a volume, or a root.
    #
    if ( $CONFIG{'lvm'} && $CONFIG{'dir'} )
    {
       print "Please only specify a volume group or a directory name - not both\n";
       exit;
    }


    #
    #  Make sure we have at least one of the lvm or root specified.
    #
    if ( (!defined( $CONFIG{'dir'} ) ) && ( !defined( $CONFIG{'lvm'} ) ) )
    {
       print "Please specify either a directory root, or an LVM volume group\n";
       exit;
    }
}
